Monday, 27 April 2009

I’ve seen quite a few slideshow controls for Silverlight. Mostly they are either retemplated listboxes (buying an easy transition effect for free) or custom controls. I’d like to see how the DomainUpDown control can be used to do a slide show. This post also takes a more indepth look at TransitioningContentControl and along the way shows how to retemplate and inherit/extend other controls.

The DomainUpDown control allows you to visualize a ‘domain’, which can be anything. In our case, our domain will consist of UIElements. The TransitionContentControl is an experimental control that I haven’t featured on this blog yet. It is a ‘convenience’ control, with which I mean to say that the control just takes care of a very common goal in Silverlight development. In this case, it will react to changes in Content by transitioning old content out and transitioning new content in. I’ve talked briefly about the control here.

By using DomainUpDown (henceforth: DUD), we will get a nice navigation system for free (up/down arrows) and the option to show our domain in a cyclic way (in other words, after you reach the end, it will jump to the first item again). Also, we have the option to allow shortcuts into the domain, by having the user type in the control. We will disable that though.

I certainly do not like the default look of the control though, with its big up/down buttons. So, we have some work ahead of us.

I’ve started out with a new SL2 application, adding references to:

  • System.Windows.Controls.Toolkit
  • System.Windows.Controls.Toolkit.Input
  • System.Windows.Controls.Toolkit.Layout

These are part of the Silverlight Toolkit.

I’ve also added a folder called Images and put some images in there. Just making it easy on myself, I’ve hardcoded those images into the Items collection of my DUD. I do not want users to be able to type in the DUD, so I set ‘IsEditable’ to false. I know the images are 300*400, so I even hardcoded the width/height of the control. I end up with this Xaml:

<UserControl
  x:Class="BlogImageSlideshow.Page"
  xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
  xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
  xmlns:inputToolkit="clr-namespace:System.Windows.Controls;assembly=System.Windows.Controls.Input.Toolkit">
  <Grid
    x:Name="LayoutRoot"
    Background="White">
    <inputToolkit:DomainUpDown
      Height="300"
      Width="400"
      IsEditable="False">
      <Image
        Source="Images/3410783929_051d93bc86.jpg" />
      <Image
        Source="Images/3412190495_4099006101.jpg" />
      <Image
        Source="Images/3413898641_f975065242.jpg" />
      <Image
        Source="Images/3414953148_cec704b7b8.jpg" />
      <Image
        Source="Images/3415344995_730b5cc814.jpg" />
    </inputToolkit:DomainUpDown>
  </Grid>
</UserControl>

 

When we view the control though, we are in for a surprise:

image

The buttons are way to big, and our image is way too small. This is caused by the way those buttons are created (with paths, set to scale uniformly). In most situations that will be exactly what you would like, however, in our case it most definitely is not. We’ll want to retemplate our control and are going to do that the ‘easy’ way: through Blend.

Once in Blend, I choose to edit the template of the DUD, which creates a style for me. The Object tab reveals the following parts of DUD:

image

To fix the ugly buttons, let’s repeat the process and retemplate the Spinner (which is a ButtonSpinner). That ButtonSpinner is responsible for hosting our content (in the image that is a Grid with two children: ‘Visualization’ and ‘Text’. It is also responsible for displaying the two buttons.

Editing the Spinner reveals these objects:

image

The unnamed [Button] is used to ‘catch’ the mouseclicks that are done on a disabled button. We don’t need it, so I’ll remove it. Also, I don’t want the buttons on the right side, but I want the previous button on the left and the next button on the right. I actually have a great idea: I’ll overlay the buttons and make them transparent. That way, pressing on the right side of the image will move the slideshow forwards and pressing on the left side will make it go backwards.

I also retemplated the buttons: removing the usual gradient, but having some overlay on ‘mouseOver’ to indicate that we can press. Since I’m no designer, I just used a one white rectangle with an opacity of 0 and a linear gradient opacitymask. On MouseOver, I will animate the opacity to 30%. The effect is a gradual whitening at the border of the image.

This is the style for one of the buttons:

<Style x:Key="RepeatButtonStyle1" TargetType="RepeatButton">
    <Setter Property="Background" Value="#FF1F3B53"/>
    <Setter Property="Foreground" Value="#FF000000"/>
    <Setter Property="Padding" Value="3"/>
    <Setter Property="BorderThickness" Value="1"/>
    <Setter Property="BorderBrush">
        <Setter.Value>
            <LinearGradientBrush EndPoint="0.5,1" StartPoint="0.5,0">
                <GradientStop Color="#FFA3AEB9" Offset="0"/>
                <GradientStop Color="#FF8399A9" Offset="0.375"/>
                <GradientStop Color="#FF718597" Offset="0.375"/>
                <GradientStop Color="#FF617584" Offset="1"/>
            </LinearGradientBrush>
        </Setter.Value>
    </Setter>
    <Setter Property="Template">
        <Setter.Value>
            <ControlTemplate TargetType="RepeatButton">
                <Grid x:Name="Root">
                    <vsm:VisualStateManager.VisualStateGroups>
                        <vsm:VisualStateGroup x:Name="CommonStates">
                            <vsm:VisualState x:Name="Normal"/>
                            <vsm:VisualState x:Name="MouseOver">
                                <Storyboard>
                                    <DoubleAnimationUsingKeyFrames BeginTime="00:00:00" Storyboard.TargetName="rectangle" Storyboard.TargetProperty="(UIElement.Opacity)">
                                        <SplineDoubleKeyFrame KeyTime="00:00:00.5000000" Value="0.3"/>
                                    </DoubleAnimationUsingKeyFrames>
                                </Storyboard>
                            </vsm:VisualState>
                            <vsm:VisualState x:Name="Pressed">
                                <Storyboard/>
                            </vsm:VisualState>
                            <vsm:VisualState x:Name="Disabled">
                                <Storyboard/>
                            </vsm:VisualState>
                        </vsm:VisualStateGroup>
                        <vsm:VisualStateGroup x:Name="FocusStates">
                            <vsm:VisualState x:Name="Focused">
                                <Storyboard/>
                            </vsm:VisualState>
                            <vsm:VisualState x:Name="Unfocused"/>
                        </vsm:VisualStateGroup>
                    </vsm:VisualStateManager.VisualStateGroups>
                    <Rectangle Height="Auto" Margin="0,0,0,0" VerticalAlignment="Stretch" Stroke="#FF000000" Opacity="0" x:Name="rectangle" Fill="#FFFFFFFF">
                        <Rectangle.OpacityMask>
                            <LinearGradientBrush EndPoint="-0.015,0.493" StartPoint="0.99,0.497">
                                <GradientStop Color="#00000000"/>
                                <GradientStop Color="#FFFFFFFF" Offset="1"/>
                            </LinearGradientBrush>
                        </Rectangle.OpacityMask>
                    </Rectangle>
                </Grid>
            </ControlTemplate>
        </Setter.Value>
    </Setter>
</Style>
And now for the fun stuff: Animating it!

Currently the status of the project is that I am able to quickly show a bunch of images and I can mouse over the image and click to show the next/previous image. We’re now going to dive into the TransitioningContentControl and use it to create a nice sliding effect.

First some background: in Silverlight and in WPF, you use a ContentControl to visualize any type of content you might have. When setting a new content, the ContentControl will just replace the current visual with a new visual (representing your new content). It uses a ContentTemplate to determine how to visualize your content.
The only feature that TransitioningContentControl adds to this game is an easy way to start an animation to visualize the actual replacing of content. Although there are some technical difficulties (more on that later), it is meant as a drop-in replacement for ContentControl.

The DomainUpDown also uses a ContentControl to show your content. The templatepart is called ‘Visualization’ and we are going to replace that with a TransitioningContentControl. All I did was replace the ContentControl tag with TransitioningContentControl in the template for DomainUpDown.

Now, here comes the catch: because an element can’t be in the visual tree twice (at the same time) in SL2 this creates a bit of a problem when using the TCC (TransitioningContentControl) with FrameworkElements. In SL3 I will solve this quite easily by creating an image out of the content and using that to ‘transition out’ but in SL2 the only easy work-around is to not use elements with this control. That is actually quite easy, because we can use the ContentTemplate to create our elements on the fly. I’ve changed the way I use DUD as follow:

<inputToolkit:DomainUpDown
    Height="300" Width="400"
    IsEditable="False" Style="{StaticResource SlideShowStyle}">
<inputToolkit:DomainUpDown.ItemTemplate>
  <DataTemplate>
    <Image Source="{Binding}" />
  </DataTemplate>
</inputToolkit:DomainUpDown.ItemTemplate>
<System:String>Images/3410783929_051d93bc86.jpg</System:String>
<System:String>Images/3412190495_4099006101.jpg</System:String>
<System:String>Images/3413898641_f975065242.jpg</System:String>
<System:String>Images/3414953148_cec704b7b8.jpg</System:String>
<System:String>Images/3415344995_730b5cc814.jpg</System:String>
</inputToolkit:DomainUpDown>

Because the TCC actually has a nice default transition, we could call it a day: there is a nice fade-in/fade-out animation going on. I like it!
However, let’s see if we can actually create a slideshow.

To start off, I went ahead and edited the template for TransitioningContentControl. In Blend3 there seems to be a bug that not the complete template is copied (making it rather useless). Blend2 works fine though.
The inevitable object screenshot shows
image

two ‘presentationSites’. Which enables you to see through my trickery: the only thing I do when content is being set, is copy the content inside ‘CurrentContentPresentation’ to ‘PreviousContentPresentation’ and start a storyboard. These are the storyboards that come out of the box:

image

Now, I fully expect you to create / add your own transitions here. To do that, you would go into xaml and add an empty storyboard. At that point you can use the designer again to style it. However, we’ll be fine by restyling the UpTransition and DownTransition.

Currently they do a nice fade and rendertransform. That doesn’t work for me though, so let’s start with the UpTransition, I’d like to have the PreviousContentPresentationSite move to the left and have the Current come in from the right.
That’s pretty easy, I just animated two rendertransforms to do that, not forgetting to set a nice keyspline for the smooth effect.

Next assignment: we’ll need to let TCC know which transition to use. It has no knowledge of the concept of up or down or whatever transitions you came up with.
To be able to tell TCC which transition to play, I expose a DependencyProperty of type string. That indicates the exact name of the transition that should be used the next time the content is changed.

There are many ways to do this. You could hook into the buttons and set the Transition manually. I believe the easiest way though, is to inherit from DomainUpDown and add the functionality yourself:

public class SlidingDomainUpDown : DomainUpDown
{
    private TransitioningContentControl _visualizer;

    public override void OnApplyTemplate()
    {
        base.OnApplyTemplate();

        _visualizer = GetTemplateChild("Visualization") as TransitioningContentControl;
    }

    protected override void OnDecrement()
    {
        if (_visualizer != null)
        {
            _visualizer.Transition = "DownTransition";
        }
        base.OnDecrement();
    }

    protected override void OnIncrement()
    {

        if (_visualizer != null)
        {
            _visualizer.Transition = "UpTransition";
        }

        base.OnIncrement();
    }
}

 

Interestingly enough, I get asked how to get to templateparts all the time, on the silverlight.net forums. Well, here you go: the simplest possible example there is. I hope this is clear!

At this point, everything works fine for me. Except that the effect is horrible! You see the complete image moving, but you would expect to only see it inside of the frame. What we need is some clipping.
I’m no Blend hero, but I pulled it off by creating a rectangle and right-clicking, choosing Path/make clipping path. Then I chose the TCC element, called ‘Visualization’ to be the clipping target. It ended up creating this xaml:

Clip="M0.5,0.5 L395.5,0.5 L395.5,295.5 L0.5,295.5 z"

We’re now at a point that I’m really happy about! The user has some feedback when he mouses over the image and the effect is quite nice. The user can click too fast though (while the transition is in motion) and I’d like to disable that:

protected override void OnValueChanging(RoutedPropertyChangingEventArgs<object> e)
{
    base.OnValueChanging(e);

    if (_visualizer != null && _visualizer.IsTransitioning)
    {
        e.Cancel = true;
    }
}

It would have been nicer to disable the buttons, but this will do for now.

Taking the extra step: adding direct shortcuts

This is becoming a huge post, but I’m having too much fun.
You have seen slideshows that have little indicators on the buttom that allow you to directly navigate to an image. I’m thinking of the Hulu.com example:

image

Let’s create those!

I started by adding a ListBox to the template of our SlidingDomainUpDown control. I cheated a bit and hardlinked the Items collection to the ItemsSource collection of that ListBox.
Then I went on to restyle the ListBoxItems to show a small preview of our item. Then I hooked up the ListBox.SelectionChanged event. When the selecteditem changes, I just set the Value of the DomainUpDown. Obviously, this could have been more elegantly done by creating a binding between the Value property and the SelectedItem property of the ListBox.  (oh well, it’s my blog! :) )
I also cheated because I did not spend the time looking at the key handling. The listbox actually takes care of some of the keyhandling, and I don’t like it.

I end up with this:

image

I set up the transition to be the DefaultTransition (which is a nice fade in/fade out). So, now the user can slide or go directly to an image. Loving it!

Try it out here and grab the solution here.

Monday, 27 April 2009 17:28:32 (Romance Standard Time, UTC+01:00)  #    Comments [60]  |  Trackback
 Friday, 17 April 2009

Jafar Husain told me that he was working on a challenge by Eric Lippert. David Anson followed and they posted solutions to the challenge on their blog. I thought it was quite a bit of fun so I quickly wrote something up that I like.

I will copy the problem from Erics website. It challenges you to transform a sequence of strings into a nicely delimited form.

(1) If the sequence is empty then the resulting string is "{}".
(2) If the sequence is a single item "ABC" then the resulting string is "{ABC}".
(3) If the sequence is the two item sequence "ABC", "DEF" then the resulting string is "{ABC and DEF}".
(4) If the sequence has more than two items, say, "ABC", "DEF", "G", "H" then the resulting string is "{ABC, DEF, G and H}". (Note: no Oxford comma!)

He noted he was mostly interested in readable code, so I came up with a nice Linq solution. However, Jafar was unimpressed and noted that performance was still important. I was traversing the sequence too often for his taste. Back to the drawing board and coming up with a less readable, but pretty quick implementation:

string lastWord = String.Empty;

StringBuilder sb = new StringBuilder();
foreach(string s in input)
{
    sb.Append(s);
    sb.Append(", ");
    lastWord = s;
}

int lastWordIndex = (sb.Length -1) - lastWord.Length - 3;

if (lastWordIndex > 0)
{
    Console.WriteLine("{" + sb.ToString(0, lastWordIndex) + " and " + lastWord + "}");
}
else if (sb.Length == 0)
{
    Console.WriteLine("{}");
}
else
{
    Console.WriteLine("{" + sb.ToString(0, sb.Length -2) + "}");
}

 

I like this solution quite a bit. Obviously not optimized the return statements, and the readability is quite a bit worse than my first attempt. But it’s short, only goes over the sequence once and should perform well.

edit: The linq version:

StringBuilder t = new StringBuilder();

            int count = input.Count();

            var middleStrings = input.Where((s, i) => i > 0 && i < count - 1);

            t.Append("{");

            t.Append(input.FirstOrDefault());

            // linq reads better than foreach.
            middleStrings.ToList().ForEach(s =>
                                              {
                                                 t.Append(", ");
                                                 t.Append(s);
                                              });

            if (count > 1)
            {
                t.Append(" and ");
                t.Append(input.LastOrDefault());
            }

            t.Append("}");

            return t.ToString();

Oh well.

Friday, 17 April 2009 05:15:54 (Romance Standard Time, UTC+01:00)  #    Comments [33]  |  Trackback
 Sunday, 12 April 2009

In a previous post I mentioned ways to speed up IE8. For me, having used Spybot a long time ago proved to be the culprit: it had added a slew of sites (hundreds) to the blocked sites zone. I removed it and all was well..

Or was it?

I noticed that IE8 still did not come up as quick as it did on my other computers. Opening a tab was fast, but opening another tab proved to be slow. Actually, I found that the first tab I opened was fast, the next one was slow, the following was fast again. Possibly some pre-creation of processes going on?

Today I finally did the smart thing and hooked up filemon to see what was actually going on. It turns out that there were still hundreds of domains somewhere hidden in my registry. They no longer showed up in any dialog box concerning security, but they were there! I removed the key and now IE8 is really fast. As in, faster than Chrome or Firefox, when opening new tabs. Incredible!

So, if you feel that IE8 is still a bit sluggish for you, go and see what the following registry key contains:

HKEY_LOCAL_MACHINE\SOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion\Internet Settings\ZoneMap\Domains

(Note that I’m on a x64 machine, hence the WOW64 node).

My solution is to delete the Domains key.

(I can’t even convey how happy I am with this simple find. We all spend a lot of our time inside our browser and I like IE8 better than other browsers. It is just a very capable and well-thought-out user experience. But if something is slow, it kills the experience!)

Sunday, 12 April 2009 01:29:47 (Romance Standard Time, UTC+01:00)  #    Comments [26]  |  Trackback
 Saturday, 11 April 2009

I believe this is going to be the last post in this series, although I bet I will have some loose Accordion posts coming up. This part is going to focus on AccordionButton, and show you how to retemplate it.

Previous posts:
Part 1 – accordion
Part 2 – accordion item
Part 3 – expandable content control
Part 4 – retemplating, real world example

We’ve already hit on a lot of the internals of AccordionItem. Expandable content control was used to easily animate the size of the content. When looking at the header though, it gets complicated all over again.

This is the important part of AccordionItem, defining both a place for the content and a place for the header:

<Border x:Name="Background" 
        Padding="{TemplateBinding Padding}" 
        BorderBrush="{TemplateBinding BorderBrush}" 
        BorderThickness="{TemplateBinding BorderThickness}" 
        CornerRadius="1,1,1,1">
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto" x:Name="rd0"/>
            <RowDefinition Height="Auto" x:Name="rd1"/>
        </Grid.RowDefinitions>
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="Auto" x:Name="cd0"/>
            <ColumnDefinition Width="Auto" x:Name="cd1"/>
        </Grid.ColumnDefinitions>

        <layoutPrimitivesToolkit:AccordionButton
                    x:Name="ExpanderButton"
          Style="{TemplateBinding AccordionButtonStyle}"
                    Content="{TemplateBinding Header}"
                    ContentTemplate="{TemplateBinding HeaderTemplate}"
                    IsChecked="{TemplateBinding IsSelected}"
                    IsTabStop="True"
                    Grid.Row="0"
                    Padding="0,0,0,0"
                    Margin="0,0,0,0"
                    FontFamily="{TemplateBinding FontFamily}"
                    FontSize="{TemplateBinding FontSize}"
                    FontStretch="{TemplateBinding FontStretch}"
                    FontStyle="{TemplateBinding FontStyle}"
                    FontWeight="{TemplateBinding FontWeight}"
                    Foreground="{TemplateBinding Foreground}"
                    VerticalContentAlignment="{TemplateBinding VerticalContentAlignment}" 
                    HorizontalAlignment="Stretch"
                    VerticalAlignment="Stretch" 
          HorizontalContentAlignment="{TemplateBinding HorizontalContentAlignment}"
          Background="{TemplateBinding Background}" />

        <layoutPrimitivesToolkit:ExpandableContentControl
                    x:Name="ExpandSite"
                    Grid.Row="1"
                    IsTabStop="False"
                    Percentage="0"
                    RevealMode="{TemplateBinding ExpandDirection}"
                    Content="{TemplateBinding Content}"
                    ContentTemplate="{TemplateBinding ContentTemplate}"
                    Margin="0,0,0,0"
                    FontFamily="{TemplateBinding FontFamily}"
                    FontSize="{TemplateBinding FontSize}"
                    FontStretch="{TemplateBinding FontStretch}"
                    FontStyle="{TemplateBinding FontStyle}"
                    FontWeight="{TemplateBinding FontWeight}"
                    Foreground="{TemplateBinding Foreground}"
                    HorizontalContentAlignment="Left"
                    VerticalContentAlignment="Top" 
                    HorizontalAlignment="Stretch"
                    VerticalAlignment="Stretch"/>
    </Grid>
</Border>


As you can see, both ExpandableContentControl and AccordionButton are placed in the primitives namespace. More importantly, they are placed in a grid. As I’ve shown in previous posts, that is how we react to the ExpandDirection setting on accordion (so placing the content in the first column and the button in the second would correspond to the ExpandDirection ‘right’).

AccordionButton is an extremely simple class. It inherits from ToggleButton, and its whole purpose in life is to be able to react (once again) to different ExpandDirections. So, if you were to open up the code of AccordionButton, you would see that all it does is this:

switch (ParentAccordionItem.ExpandDirection)
{
    case ExpandDirection.Down:
        VisualStates.GoToState(this, useTransitions, VisualStates.StateExpandDown);
        break;

    case ExpandDirection.Up:
        VisualStates.GoToState(this, useTransitions, VisualStates.StateExpandUp);
        break;

    case ExpandDirection.Left:
        VisualStates.GoToState(this, useTransitions, VisualStates.StateExpandLeft);
        break;

    default:
        VisualStates.GoToState(this, useTransitions, VisualStates.StateExpandRight);
        break;
}

Ofcourse, the template of AccordionButton is quite big. This is the reason I introduced this class, just to simplify the template (considerably).

It is time to look at the important part of AccordionButtons template:

<Border x:Name="background" Background="{TemplateBinding Background}" CornerRadius="1,1,1,1">
    <Grid>
        <Border Height="Auto" Margin="0,0,0,0" x:Name="ExpandedBackground" VerticalAlignment="Stretch" Opacity="0" Background="#FFBADDE9" BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}" CornerRadius="1,1,1,1"/>
        <Border Height="Auto" Margin="0,0,0,0" x:Name="MouseOverBackground" VerticalAlignment="Stretch" Opacity="0" Background="#FFBDBDBD" BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}" CornerRadius="1,1,1,1"/>
        <Grid Background="Transparent">
            <Grid.ColumnDefinitions>
                <ColumnDefinition Width="Auto" x:Name="cd0"/>
                <ColumnDefinition Width="Auto" x:Name="cd1"/>
            </Grid.ColumnDefinitions>
            <Grid.RowDefinitions>
                <RowDefinition Height="Auto" x:Name="rd0"/>
                <RowDefinition Height="Auto" x:Name="rd1"/>
            </Grid.RowDefinitions>
            <Grid Height="19" HorizontalAlignment="Center" x:Name="icon" VerticalAlignment="Center" Width="19" RenderTransformOrigin="0.5,0.5" Grid.Column="0" Grid.Row="0">
                <Grid.RenderTransform>
                    <TransformGroup>
                        <ScaleTransform/>
                        <SkewTransform/>
                        <RotateTransform Angle="-90"/>
                        <TranslateTransform/>
                    </TransformGroup>
                </Grid.RenderTransform>
                <Path 
                    Height="Auto" 
                    HorizontalAlignment="Center" 
                    Margin="0,0,0,0" x:Name="arrow" 
                    VerticalAlignment="Center" 
                    Width="Auto" 
                    RenderTransformOrigin="0.5,0.5" 
                    Stroke="#666" 
                    StrokeThickness="2" 
                    Data="M 1,1.5 L 4.5,5 L 8,1.5">
                    <Path.RenderTransform>
                        <TransformGroup>
                            <ScaleTransform/>
                            <SkewTransform/>
                            <RotateTransform/>
                            <TranslateTransform/>
                        </TransformGroup>
                    </Path.RenderTransform>
                </Path>
            </Grid>
            <layoutToolkit:LayoutTransformer
                FontFamily="{TemplateBinding FontFamily}" 
                FontSize="{TemplateBinding FontSize}" 
                FontStretch="{TemplateBinding FontStretch}" 
                FontStyle="{TemplateBinding FontStyle}" 
                FontWeight="{TemplateBinding FontWeight}" 
                Foreground="{TemplateBinding Foreground}" 
                HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
                Margin="6,6,6,0" 
                x:Name="header" 
                Grid.Column="0"
                Grid.Row="0" 
                Grid.RowSpan="1" 
                Content="{TemplateBinding Content}" 
                ContentTemplate="{TemplateBinding ContentTemplate}"/>
        </Grid>
    </Grid>
</Border>

There are two parts here. We have a grid (named ’icon’) and a LayoutTransformer. Again, we see the old trick of a grid with 2 columns and 2 rows: it is used to place the icon relative to the header.

Layouttransformer: it is a class that my teammate Delay has developed and was able to ship with the toolkit. He has a whole host of blog posts about it, and his latest may be of most interest to you: it shows how to animate the layoutTransformer.
LayoutTransformer is a control that allows me to rotate the header and keeping the layout engine happy. In other words, I rotate the header 90 degrees, and the width and height of the header is correct. That would not be the case if I had used RenderTransform.
I will not focus on this class any more, please checkout Delays posts on it!

By now, you should have enough understanding of what is going on to be able to retemplate AccordionButton:

  1. It holds both the Header and an icon
  2. It reacts to expanddirection
  3. It re-arranges the location of the button and header accordingly

Actually, it also rotates the icon. So when you are in ExpandDirection ‘right’, the arrow in the button will still point towards the content, even though its location is completely different from ExpandDirection ‘left’.

Templating the AccordionButton

We have hidden AccordionButton from the designer surface. We did that because we feel that, as a primitive, there is no value in having AccordionButton clutter up the design toolbox. However, in order to template it, it is easiest to use blend with only an AccordionButton there.. So I used this Xaml in Blend:

<UserControl x:Class="AccordionBlogSamplesSL2.HorizontalImages"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 
    xmlns:layoutToolkit="clr-namespace:System.Windows.Controls;assembly=System.Windows.Controls.Layout.Toolkit" 
    xmlns:vsm="clr-namespace:System.Windows;assembly=System.Windows" xmlns:System_Windows_Controls_Primitives="clr-namespace:System.Windows.Controls.Primitives;assembly=System.Windows.Controls.Layout.Toolkit" 
    >
  <Grid x:Name="LayoutRoot" Background="White">
      <System_Windows_Controls_Primitives:AccordionButton />
  </Grid>
</UserControl>

That gives me a nice design surface with an AccordionButton on it. When I attempt to ‘Edit template’, Blend will create the style and shows the objects inside of AccordionButton:

image

Not only that, it also shows me these states:

image

When pressing any of the ExpandDirectionStates, you will notice the icon being rotated and moved to different gridcells. It is easiest to remain in the Base state to do your work.

I’m not a terribly good designer (one would argue I am in fact, a terribly bad designer), so I would even attempt to make this look good. I will just change the template slightly so I have something to show you!

The design surface looks like this:

image

I will change the path (called arrow) to this:

image

I’ve also changed the MouseOverBackground to something random and the expanded background.
Quite a few people have asked me how to change those properties, well, here they are. They are part of the AccordionButton template.

Bringing it all together

AccordionItem luckily exposes an AccordionButtonStyle, so in order to restyle our AccordionItems to accept this AccordionButtonStyle, we simply apply it.

<layoutToolkit:Accordion>
    <layoutToolkit:AccordionItem Content="AccordionItem" AccordionButtonStyle="{StaticResource AccordionButtonStyle1}"/>
    <layoutToolkit:AccordionItem Content="AccordionItem" AccordionButtonStyle="{StaticResource AccordionButtonStyle1}"/>
</layoutToolkit:Accordion>

As I’ve been getting some questions on my blog about styles and templates, I will also show you how to do this with an AccordionItemContainerStyle:

<layoutToolkit:Accordion>
<layoutToolkit:Accordion.ItemContainerStyle>
    <Style TargetType="layoutToolkit:AccordionItem">
     <Setter Property="AccordionButtonStyle" Value="{StaticResource AccordionButtonStyle1}" />
    </Style>
</layoutToolkit:Accordion.ItemContainerStyle>
    <layoutToolkit:AccordionItem Content="AccordionItem" />
    <layoutToolkit:AccordionItem Content="AccordionItem" />
</layoutToolkit:Accordion>

(The latter approach is best, imho)

We end up with:

image

I hope that helps!

Saturday, 11 April 2009 21:42:21 (Romance Standard Time, UTC+01:00)  #    Comments [155]  |  Trackback
 Monday, 06 April 2009

Mehdi pointed me to a great accordion on this site. It is a horizontal accordion that only uses images. In this post I’ll walk us through creating something similar.

This is what the accordion looks like:image

There is a snazzy effect where the headline pops in from below when you open the item. Go look at it and play a bit, it’s pretty cool!

I started out by adding references to the highlighted 3 dll’s. All 3 are necessary!

image

Then I created my accordion and gave it some fixed size accordion items. I used this Xaml:

    <layoutToolkit:Accordion ExpandDirection="Right" Margin="100">
      <layoutToolkit:AccordionItem>
        <Rectangle Width="150" Height="80" Fill="Blue" />
      </layoutToolkit:AccordionItem>
      <layoutToolkit:AccordionItem>
        <Rectangle Width="150" Height="80" Fill="Red" />
      </layoutToolkit:AccordionItem>
      <layoutToolkit:AccordionItem>
        <Rectangle Width="150" Height="80" Fill="Green" />
      </layoutToolkit:AccordionItem>
      <layoutToolkit:AccordionItem>
        <Rectangle Width="150" Height="80" Fill="Yellow" />
      </layoutToolkit:AccordionItem>
      <layoutToolkit:AccordionItem>
        <Rectangle Width="150" Height="80" Fill="PowderBlue" />
      </layoutToolkit:AccordionItem>
    </layoutToolkit:Accordion>

As you can see, I set the ExpandDirection to “Right”, so the accordion opens up in the same way as our sample. I have not set a specific Width on the Accordion, which means that the Accordion will only take what it needs.

image

I get a lot of questions on how the Width of the Accordion determines sizing on AccordionItems. You need to remember that the Items will always divide the space equally they get. In our example, setting a fixed Width of 500 on the Accordion would have yielded this result:

image

Now, our AccordionItems exist always of a header and content. In our sample though, we do not see a header at all. So, we will need to retemplate AccordionItem in order to remove that header.
There is a headerTemplate property on Accordion, but keep in mind that that will allow you to determine the look of what goes into the header! In our case, we will completely remove the header from AccordionItem. So, I went into Blend and selected the first AccordionItem and opened up its template:

image

This created a styleResource that is being applied to the first item.
In the template, I removed every element that I don’t care about. The AccordionButton is the little arrow that moves. Leaving me with this:

image

However, we do actually need a header, since it will be the element a user can click to navigate the accordion. So, let’s look at the Grid that hosts ExpandSite:

<Grid>
<Grid.RowDefinitions>
    <RowDefinition Height="Auto" x:Name="rd0"/>
    <RowDefinition Height="Auto" x:Name="rd1"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
    <ColumnDefinition Width="Auto" x:Name="cd0"/>
    <ColumnDefinition Width="Auto" x:Name="cd1"/>
</Grid.ColumnDefinitions>
<System_Windows_Controls_Primitives:ExpandableContentControl 
        HorizontalAlignment="Stretch" 
        Margin="0,0,0,0" 
        x:Name="ExpandSite" 
        VerticalAlignment="Stretch" 
        Grid.Row="1" 
        FontFamily="{TemplateBinding FontFamily}" 
        FontSize="{TemplateBinding FontSize}" 
        FontStretch="{TemplateBinding FontStretch}" 
        FontStyle="{TemplateBinding FontStyle}" 
        FontWeight="{TemplateBinding FontWeight}" 
        Foreground="{TemplateBinding Foreground}" 
        HorizontalContentAlignment="Left" 
        IsTabStop="False" 
        VerticalContentAlignment="Top" 
        Content="{TemplateBinding Content}" 
        ContentTemplate="{TemplateBinding ContentTemplate}" 
        Percentage="0" 
        RevealMode="{TemplateBinding ExpandDirection}"/>
</Grid>

You can see that the grid has 2 rows and 2 columns. This is all that is needed to position the header and content in all the possible expandDirections. Just for fun, I’ll show you the VisualState “ExpandedRight”:

<vsm:VisualState x:Name="ExpandRight">
    <Storyboard>
        <ObjectAnimationUsingKeyFrames Duration="0" Storyboard.TargetName="ExpandSite" Storyboard.TargetProperty="(Grid.ColumnSpan)">
            <DiscreteObjectKeyFrame KeyTime="0" Value="1"/>
        </ObjectAnimationUsingKeyFrames>
        <ObjectAnimationUsingKeyFrames Duration="0" Storyboard.TargetName="ExpandSite" Storyboard.TargetProperty="(Grid.RowSpan)">
            <DiscreteObjectKeyFrame KeyTime="0" Value="2"/>
        </ObjectAnimationUsingKeyFrames>
        <ObjectAnimationUsingKeyFrames Duration="0" Storyboard.TargetName="ExpandSite" Storyboard.TargetProperty="(Grid.Row)">
            <DiscreteObjectKeyFrame KeyTime="0" Value="0"/>
        </ObjectAnimationUsingKeyFrames>
        <ObjectAnimationUsingKeyFrames Duration="0" Storyboard.TargetName="ExpandSite" Storyboard.TargetProperty="(Grid.Column)">
            <DiscreteObjectKeyFrame KeyTime="0" Value="1"/>
        </ObjectAnimationUsingKeyFrames>
        <ObjectAnimationUsingKeyFrames Duration="0" Storyboard.TargetName="rd0" Storyboard.TargetProperty="Height">
            <DiscreteObjectKeyFrame KeyTime="0" Value="*"/>
        </ObjectAnimationUsingKeyFrames>
        <ObjectAnimationUsingKeyFrames Duration="0" Storyboard.TargetName="cd1" Storyboard.TargetProperty="Width">
            <DiscreteObjectKeyFrame KeyTime="0" Value="*"/>
        </ObjectAnimationUsingKeyFrames>
    </Storyboard>
</vsm:VisualState>

As you can see, it is happily placing items in the correct location and setting heights and widths on the grid cells. Since we do want the header to have a width, I’ll give the first column (where our header would have been) a minWidth. I will also remove all the Expand visualstates since I don’t need those and they only serve to bloat the Xaml at this point.

Since we removed AccordionButton, we have nothing to press and open the Item. But that’s not what we want anyway, we want to open items as we mouse over them. So I’ve introduced an eventhandler for the MouseEnter event on AccordionItem:

void item_MouseEnter(object sender, MouseEventArgs e)
{
    AccordionItem item = (AccordionItem) sender;
    item.IsSelected = true;
}

Now we’re getting somewhere! The items behave quite similar to our sample. Still need to figure out the header and content parts though. The ExpandSite will always contract to 0 width when it is collapsed (not selected). That can’t be what we want.

We removed the header because we did not want it, and want a part of the content to be visible. There are two animations that govern the expand and collapse movements. They are here:

<vsm:VisualStateGroup x:Name="ExpansionStates">
    <vsm:VisualStateGroup.Transitions>
        <vsm:VisualTransition GeneratedDuration="0"/>
    </vsm:VisualStateGroup.Transitions>
    <vsm:VisualState x:Name="Collapsed">
        <Storyboard>
            <DoubleAnimationUsingKeyFrames BeginTime="00:00:00" Storyboard.TargetName="ExpandSite" Storyboard.TargetProperty="(ExpandableContentControl.Percentage)">
                <SplineDoubleKeyFrame KeySpline="0.2,0,0,1" KeyTime="00:00:00.3" Value="0"/>
            </DoubleAnimationUsingKeyFrames>
        </Storyboard>
    </vsm:VisualState>
    <vsm:VisualState x:Name="Expanded">
        <Storyboard>
            <DoubleAnimationUsingKeyFrames BeginTime="00:00:00" Storyboard.TargetName="ExpandSite" Storyboard.TargetProperty="(ExpandableContentControl.Percentage)">
                <SplineDoubleKeyFrame KeySpline="0.2,0,0,1" KeyTime="00:00:00.3" Value="1"/>
            </DoubleAnimationUsingKeyFrames>
        </Storyboard>
    </vsm:VisualState>
</vsm:VisualStateGroup>

Let me explain what is going on there. An AccordionItem can be in either Collapsed or Expanded state. I have no way of determining how big an item should be when it is expanded. Remember, it is the accordion that calculates such things at runtime (given the amount of items, and the width of the accordion itself). When the accordion sets a ‘targetsize’, we also calculate a percentage. When we are collapsed, that percentage is 0, when we are fully expanded, that percentage is 1.

The storyboards that you can see here are being started after the AccordionItem has been collapsed or expanded. In other words, they actually do the revealing or hiding of the content. The only thing they have to animate is the percentage, and this way I allow designers to come in and create different kind of animations (changing the keyspline for instance).

The important value here is 0, in the Collapsed storyboard. Turns out we don’t want it to collapse to 0, but to something like 0.2! If we change that storyboard, the item will still be partially visible, even though the accordion feels it is completely collapsed.

image

That was easy. So, let’s start working on some cool headline action.
I’d like to have a headline come in from below as we expand.

So, let’s create a ContentControl to display our header:

                        <Grid>
                            <System_Windows_Controls_Primitives:ExpandableContentControl 
                                HorizontalAlignment="Stretch" 
                                Margin="0,0,0,0" 
                                x:Name="ExpandSite" 
                                VerticalAlignment="Stretch" 
                                HorizontalContentAlignment="Left" 
                                IsTabStop="False" 
                                VerticalContentAlignment="Top" 
                                Content="{TemplateBinding Content}" 
                                ContentTemplate="{TemplateBinding ContentTemplate}" 
                                Percentage="0" 
                                RevealMode="{TemplateBinding ExpandDirection}"/>
                            <ContentControl x:Name="header" 
                                Content="{TemplateBinding Header}" 
                          VerticalAlignment="Bottom" 
                          Visibility="Collapsed" RenderTransformOrigin="0.5,0.5" >
                                <ContentControl.RenderTransform>
                                    <TransformGroup>
                                        <ScaleTransform/>
                                        <SkewTransform/>
                                        <RotateTransform/>
                                        <TranslateTransform/>
                                    </TransformGroup>
                                </ContentControl.RenderTransform>
                            </ContentControl>
                        </Grid>
                    </Border>
                </Grid>

And have Blend generate some nice animation effects:

<vsm:VisualState x:Name="Expanded">
<Storyboard>
    <DoubleAnimationUsingKeyFrames BeginTime="00:00:00" Storyboard.TargetName="ExpandSite" Storyboard.TargetProperty="(ExpandableContentControl.Percentage)">
        <SplineDoubleKeyFrame KeySpline="0.2,0,0,1" KeyTime="00:00:00.3" Value="1"/>
    </DoubleAnimationUsingKeyFrames>
    <ObjectAnimationUsingKeyFrames BeginTime="00:00:00" Storyboard.TargetName="header" Storyboard.TargetProperty="(UIElement.Visibility)">
        <DiscreteObjectKeyFrame KeyTime="00:00:00.2000000">
            <DiscreteObjectKeyFrame.Value>
                <Visibility>Visible</Visibility>
            </DiscreteObjectKeyFrame.Value>
        </DiscreteObjectKeyFrame>
    </ObjectAnimationUsingKeyFrames>
    <DoubleAnimationUsingKeyFrames BeginTime="00:00:00" Storyboard.TargetName="header" Storyboard.TargetProperty="(UIElement.RenderTransform).(TransformGroup.Children)[3].(TranslateTransform.Y)">
        <SplineDoubleKeyFrame KeyTime="00:00:00.2000000" Value="60"/>
        <SplineDoubleKeyFrame KeyTime="00:00:00.4000000" Value="0" KeySpline="0,0,0.46900001168251,1"/>
    </DoubleAnimationUsingKeyFrames>
</Storyboard>

You can see that the header starts out with Visibility=Collapsed, this way it won’t take up any space and mess up the AccordionItem.
When the item expands, that visibility is toggled and a rendertransform is used to bring it into view.

Let’s finish it off with some images, to make our accordion look nice! I grabbed some images and created a somewhat better header:

  <layoutToolkit:AccordionItem Style="{StaticResource AccordionItemStyle1}" MouseEnter="item_MouseEnter">
  <layoutToolkit:AccordionItem.Header>
    <Border Background="#aa000000" Width="400" Height="80">
      <StackPanel Margin="10">
        <TextBlock FontFamily="Verdana" FontSize="20" Foreground="White">Seamonster</TextBlock> 
        <TextBlock FontFamily="Verdana" FontSize="12" Foreground="White">by Proudlove</TextBlock> 
      </StackPanel>
    </Border>
  </layoutToolkit:AccordionItem.Header>
  <Image Source="/Images/3410783929_051d93bc86.jpg"  />
</layoutToolkit:AccordionItem>

I also changed the animations slightly, to be somewhat slower. The result looks like this:

image

image

I hope this little walkthrough helped.

See the live sample here, and download the project here.
Let me know what you think!

Monday, 06 April 2009 04:37:46 (Romance Standard Time, UTC+01:00)  #    Comments [52]  |  Trackback
 Sunday, 05 April 2009

The company where I work uses discussion lists quite religiously, and I’m sure other companies do as well. A discussion list or group allows you to subscribe to emails about a particular subject, for instance ‘silverlight’. People can send email to the group and everyone that has subscribed will receive it.

The problem is that this can become chaotic pretty quickly. Of course, everyone will setup a rule in Outlook to move incoming mail from a group to a particular folder, but I’m interested in creating a workflow that will help me stay on top of all of those emails, all the time. That is hard, mainly because you will keep getting email from conversations that you do not care about. Some of these conversations take days to come to a conclusion, making you manually wade through all of that over and over again.

There are many systems devised to deal with email stress and organizing your life inside Outlook. One important system is GTD (Getting Things Done). I find that those do not directly apply to email received from discussion lists.

What is needed, is some way to kill a thread and not be bothered with it again. There are programs that will allow you to do that and I’ve played around with all of them.
However, none quite suited me, maybe because I don’t trust programs to delete email. There is one that is called ThreadKiller, which did not install for me. I believe it does the same as I’m describing here.

I am interested in the following workflow:

  1. email comes, either new threads or replies to old threads
  2. I will look at the new threads and decide if I’m interested in them or not
  3. Threads that I no longer want, should be moved to a folder
  4. When I have time to actually read whole conversations, of the threads that remain, I will first make sure that new replies to threads are removed

Basically it boils down to finding some way to easily find and delete mail that belongs to threads I don’t want any more. Easier said than done! I ended up having to drop to VB macros, which I really wanted to avoid.

Here is a description of my current setup.

Step 1: searchfolder to manage new threads

Each discussion list I am on will have a searchfolder that only shows me ‘new’ mail. You can create one by adding a search folder, and using the ‘advanced’ tab to setup these two criteria:

1. In Folder is (exactly) –the name of the folder that has the mail from the group -
2. Subject doesn’t contain RE:

Step 2: setup a delete staging folder

Create a folder called Delete staging. It will contain the start of threads that you are no longer interested in. Basically I will have a macro later on, that will look in this folder and remove all the mail that belong to the same subject.

Step 3: easily move new threads to the delete staging folder

I’m a keyboard junkie, and I want to easily move emails to that folder.
I dropped into the VB Macro editor and used this code:

Sub MoveToDeleteStaging()
    Dim objItem As Outlook.MailItem
    Set objItem = Application.ActiveExplorer.Selection.Item(1)
        Dim objNamespace As NameSpace
    Dim objInboxFolder As Outlook.MAPIFolder
        Set objNamespace = Application.GetNamespace("MAPI")
    Set objInboxFolder = objNamespace.GetDefaultFolder(olFolderInbox)
    Set deleteFolder = objInboxFolder.Folders("Delete staging")
    
    objItem.Move (deleteFolder)
End Sub

Even though I despise VB, this code is quite simple indeed. You can see that I hard coded the folder “Delete staging” in there.

Now, customize the toolbar to add this macro there. Rename it to something like ‘&Delete Thread’, using the ampersand to indicate the shortcut key.

When you select a mail item and you execute the macro, it will move the item to the delete staging folder.

Step 4: scrub your folder

The real work is to make sure that mail you receive gets deleted. The best way might be to create a rule to do that as the mail comes in. I don’t like that, because I get a kick out of seeing how much mail was removed. So for now I use a manual process. The code can be easily adjusted to run as a rule when new mail arrives.

So, being a VB newbie, I’ve written code that is vey inefficient but luckily very useful. The macro below will iterate through all the items in the delete staging folder and remove any mail it finds in the folder that you are scrubbing.

Sub DeleteMessagesThatAreInDeleteStagingFolder()
    Dim deleteFolder As Outlook.Folder
    Dim currentFolder As Outlook.Folder
    Dim runningItem As Outlook.MailItem
    Dim threadItems As Outlook.Items
    Dim itemToDelete As Outlook.MailItem
    Dim objNamespace As NameSpace
    Dim objInboxFolder As Outlook.MAPIFolder
    Dim Filter As String
        
    Set objNamespace = Application.GetNamespace("MAPI")
    Set objInboxFolder = objNamespace.GetDefaultFolder(olFolderInbox)
    Set deleteFolder = objInboxFolder.Folders("Delete staging")
        
    Set currentFolder = Application.ActiveExplorer.currentFolder
    
    For Each runningItem In deleteFolder.Items
        Filter = "@SQL=" & Chr(34) & _
            "urn:schemas:httpmail:thread-topic" & _
            Chr(34) & "= '" & Replace(runningItem.ConversationTopic, "'", "''") & "'"
        Set threadItems = currentFolder.Items.Restrict(Filter)
        For Each itemToDelete In threadItems
            itemToDelete.Delete
        Next
    Next
    
End Sub

The code uses a filter in the dsal language (some sort of SQL wannabe language used by Outlook) to filter the email in the folder so it can then delete it.

Again, I created a toolbar shortcut for it.

Usage:

Just go to your search folder and quickly triage all the mail that is there by either reading the mail or moving it to the delete staging folder. Then go to the actual folder and scrub it using the second macro. This will remove all the threads that you were not interested in, leaving you with threads to you do want to read!!

I hope that is useful to someone.

Sunday, 05 April 2009 00:45:09 (Romance Standard Time, UTC+01:00)  #    Comments [18]  |  Trackback