Monday, April 06, 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, April 06, 2009 4:37:46 AM (Romance Standard Time, UTC+01:00)  #    Comments [52]  |  Trackback
 Sunday, April 05, 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, April 05, 2009 12:45:09 AM (Romance Standard Time, UTC+01:00)  #    Comments [18]  |  Trackback
 Sunday, March 29, 2009

We continue our exploration of Accordion. This time we’ll take a look at some of the template parts, namely ExpandableContentControl. See part 1 and part 2 if you didn’t yet.

ExpandableContentControl

This control is part of AccordionItem and is used to display the content of an accordionItem. It sits in the Primitives namespace, which means it will be used primarily as a part of a template. It solves an important problem: being able to resize (expand/contract) a piece of content, without triggering resize actions on the content itself. With that I mean: if your AccordionItem would use a Grid or a DockPanel as it’s content and I would just be animating the width or the height, you would see the panel changing as it tries to keep up with the new sizes. Ofcourse, that would not be the case if you had set an explicit width or height to the panel, but AccordionItem does not force you to do so.So, during the resize of our AccordionItem, I don’t want the content to have to resize.

Furthermore, it would be nice to make it really easy for you to determine how the animation should look like. Do you want a gradual easein/out, a bump or something different altogether? I want to be able to design my animation in Blend, using the nifty keyspline features!

I accomplish the above goals by throwing in a ScrollViewer and defining a DependencyProperty: Percentage.

ScrollViewer allows Accordion to just determine the size the item will eventually be (after expand action) and set it only once. At that moment the content of the scrollviewer is going to resize itself accordingly. Then, as we animate the width or height of the scrollviewer, its content is none-the-wiser. Hence, no ugly resize actions going on.

Making the actual animation be easily defined inside of Blend, is done by the above-mentioned Percentage DP.
Accordion only determines the size the AccordionItem will eventually be, I call it the TargetSize. ExpandableContentControl will calculate and set its actual size using to the following simple function: actualsize = percentage * TargetSize.

Accordion also triggers a visualstate called either Expanded or Contracted on AccordionItem. Those animate the percentage property on ExpandableContentControl. See this Xaml in the template of AccordionItem:

   62 <vsm:VisualState x:Name="Collapsed">

   63     <Storyboard>

   64         <DoubleAnimationUsingKeyFrames

   65             BeginTime="00:00:00"

   66             Storyboard.TargetName="ExpandSite"

   67             Storyboard.TargetProperty="(ExpandableContentControl.Percentage)">

   68             <SplineDoubleKeyFrame

   69                 KeyTime="00:00:00.3"

   70                 KeySpline="0.2,0,0,1"

   71                 Value="0" />

   72         </DoubleAnimationUsingKeyFrames>

   73     </Storyboard>

   74 </vsm:VisualState>

   75 <vsm:VisualState x:Name="Expanded">

   76     <Storyboard>

   77         <DoubleAnimationUsingKeyFrames

   78             BeginTime="00:00:00"

   79             Storyboard.TargetName="ExpandSite"

   80             Storyboard.TargetProperty="(ExpandableContentControl.Percentage)">

   81             <SplineDoubleKeyFrame

   82                 KeyTime="00:00:00.3"

   83                 KeySpline="0.2,0,0,1"

   84                 Value="1" />

   85         </DoubleAnimationUsingKeyFrames>

   86     </Storyboard>

   87 </vsm:VisualState>

This makes it possible to design your animation at the correct level (AccordionItem) and makes it a very design friendly experience, because Blend allows you to edit keysplines as follows:

image

(SL3 is adding a nice collection of other spline types that makes elastic AccordionItems even easier!)

I’ll discuss the remaining points of interest.

RevealMode

Since I expose one property (Percentage) for you to animate, the control needs to know whether that responds to the vertical or horizontal direction. Thus, AccordionItem will set the correct RevealMode, corresponding to its own ExpandDirection.

TargetSize

Already mentioned, setting the TargetSize will set the size of the ScrollViewer. It will also recalculate the percentage. Let’s say I have an item that has a height of 100. Now another item is collapsed, so the Accordion decides the remaining items should have a height of 160. The percentage that was once at 1 is recalculated to be 0.625.
This way we don’t have to restart the full animation, but we can just resize nicely.


I hope that helps understanding this vital piece of the template. In the future, I will either remove it (if the outlined problems can be solved more gracefully in another way) or expose the style of ExpandableContentControl in AccordionItem, so it can be more easily styled.

Sunday, March 29, 2009 1:01:18 AM (Romance Standard Time, UTC+01:00)  #    Comments [63]  |  Trackback
 Saturday, March 28, 2009

Completely unrelated to anything on this site, I wanted to talk about IE8 for a minute. It looks like a great browser, but on my home machine, just spinning up a tab took about 3 seconds. When I noticed that other people did not have this problem, I started to investigate. These are the two tips that I’ve found:

The first is by Ed Bot and basically tells you to execute ‘regsvr32 actxprxy.dll’ from an elevated command prompt. That registers a dll that in certain configurations is not registered. It can make a huge difference.

The second on is the the one that saved me. I don’t know the source, but there are several blogs telling you to look at your restricted sites (Tools/InternetOptions/Security/Restricted Sites, press the button labeled ‘sites’ ).
I had used spybot long ago, which had added a huge list of sites there. Apparently this can really slow down outlook and ie!
Remove them by resetting your settings to default (Advanced tab, button labeled ‘reset’).

Now I have a blindingly fast internet explorer, and I must admit, I do love this browser now. Switching back!

Saturday, March 28, 2009 11:08:28 PM (Romance Standard Time, UTC+01:00)  #    Comments [29]  |  Trackback