Thursday, November 20, 2008

This post will show you how to write a very simple contentControl, that allows you to transition your old content with your new content. It is a little prototype I put together after wanting to ‘spice up’ another control I was working on!

Show me!

What?

A contentControl is a control that only has one reason in life: to show your content. You will use it in places where you have an arbitrary object that you wish to display. For instance, a ‘Person’ business object, or a ‘string’. Its two most useful properties are ‘Content’ and ‘ContentTemplate’. The latter will let you define how to show your person.

By bundling two contentControls, we can move the new content to the upper contentControl and show the old content in the lower contentControl. Then we can use blend to think up some great transition between those visuals.

Why?

The current contentControl does not allow you to do a transition effect because it has no concept of ‘old content’. This means the change can not be visualized in a rich manner. The only thing you could do is transition in the new content, but the old content will be gone instantly.

Why not?

So, the Silverlight team could have just build such an approach into the framework, why didn’t they? Well, I have not checked officially, but can come up with a bunch of reasons. Most of all, this will keep a reference to old content (for the time the storyboard runs) that you might not expect. Not a big deal on a string, but possibly a very big deal on a large business object. Also, using this approach will result in a much larger object graph inside of the visual tree than just a contentControl: we’re adding a second contentControl, a layout panel, two visualstates and a few storyboards. Finally, creation of a template is costly and now happens twice when the content gets changed.
So, it is not a good idea to use this control all over the place. Please use sparingly, with caution and very lightweight templates.

How?

It’s a pretty straightforward control. (I’ll not do the pictures thing this time.. let’s see how copying regular code feels!)

    9     [TemplateVisualState(Name = "Transitioning", GroupName = "PresentationStates")]

   10     [TemplateVisualState(Name = "Normal", GroupName = "PresentationStates")]

   11     [TemplatePart(Name = "PreviousContentPresentationSite", Type = typeof(ContentControl))]

   12     public class TransitioningControl : ContentControl

   13     {

   14         private ContentControl PreviousPresentationSite { get; set; }

   15 

   16         public TransitioningControl()

   17         {

   18             DefaultStyleKey = typeof(TransitioningControl);

   19         }

Like I said, we’ll need a second contentControl to host our old content. This is represented by line 11.

Pretty interesting is the onApplyTemplate:

   21         public override void OnApplyTemplate()

   22         {

   23             base.OnApplyTemplate();

   24 

   25             PreviousPresentationSite = GetTemplateChild("PreviousContentPresentationSite") as ContentControl;

   26 

   27             Panel root = GetTemplateChild("LayoutRoot") as Panel;

   28 

   29             Storyboard sb= null;

   30 

   31             if (root != null)

   32             {

   33                 sb =

   34                     (from stategroup in (VisualStateManager.GetVisualStateGroups(root) as Collection<VisualStateGroup>)

   35                      where stategroup.Name == "PresentationStates"

   36                      from state in (stategroup.States as Collection<VisualState>)

   37                      where state.Name == "Transitioning"

   38                      select state.Storyboard).FirstOrDefault();

   39             }

   40 

   41             if (sb != null)

   42             {

   43                 sb.Completed += ((sender, e) =>

   44                                      {

   45                                          // go to normal state and release our hold on the old content.

   46                                          VisualStateManager.GoToState(this, "Normal", false);

   47                                          if (PreviousPresentationSite != null)

   48                                          {

   49                                              PreviousPresentationSite.Content = null;

   50                                          }

   51                                      });

   52             }

   53 

   54             VisualStateManager.GoToState(this, "Normal", false);

   55         }

I’m looking for the Transitioning State and take its StoryBoard. Now I can register some code to execute when the storyboard finishes. This represents our ‘cleanup’.
Obviously, this is prototyping code, so do not just copy this and use in a production system! At the very least you would need to take care of unhooking the event, etc. etc.

   57         protected override void OnContentChanged(object oldContent, object newContent)

   58         {

   59             if (PreviousPresentationSite != null)

   60             {

   61                 PreviousPresentationSite.Content = oldContent;

   62 

   63                 base.OnContentChanged(oldContent, newContent);

   64 

   65                 // if busy with transitioning, let's skip to normal

   66                 VisualStateManager.GoToState(this, "Normal", false);

   67                 // and start our state

   68                 VisualStateManager.GoToState(this, "Transitioning", true);

   69             }

   70         }

So, when our content changes, we get a very convenient ‘old content’ object.

I just quickly change to Normal state, which probably already happened (line 46), but if the content changes again before the storyboard finishes, it looks better to start afresh.

This is my attempt at a transition:

                        <vsm:VisualStateManager.VisualStateGroups>
                            <vsm:VisualStateGroup x:Name="PresentationStates">
                                <vsm:VisualState x:Name="Transitioning">
                                    <Storyboard>
                                        <DoubleAnimationUsingKeyFrames BeginTime="00:00:00" Storyboard.TargetName="CurrentContentPresentationSite" 
Storyboard.TargetProperty="(UIElement.RenderTransform).(TransformGroup.Children)[3].(TranslateTransform.Y)">
                                            <SplineDoubleKeyFrame KeyTime="00:00:00" Value="-40"/>
                                            <SplineDoubleKeyFrame KeyTime="00:00:00.800" Value="0"/>
                                        </DoubleAnimationUsingKeyFrames>
                                        <DoubleAnimationUsingKeyFrames BeginTime="00:00:00" Storyboard.TargetName="CurrentContentPresentationSite" 
Storyboard.TargetProperty="(UIElement.Opacity)">
                                            <SplineDoubleKeyFrame KeyTime="00:00:00" Value="0"/>
                                            <SplineDoubleKeyFrame KeyTime="00:00:00.800" Value="1"/>
                                        </DoubleAnimationUsingKeyFrames>
                                        <DoubleAnimationUsingKeyFrames BeginTime="00:00:00" Storyboard.TargetName="PreviousContentPresentationSite" 
Storyboard.TargetProperty="(UIElement.Opacity)">
                                            <SplineDoubleKeyFrame KeyTime="00:00:00" Value="1"/>
                                            <SplineDoubleKeyFrame KeyTime="00:00:00.800" Value="0"/>
                                        </DoubleAnimationUsingKeyFrames>
                                        <DoubleAnimationUsingKeyFrames BeginTime="00:00:00" Storyboard.TargetName="PreviousContentPresentationSite" 
Storyboard.TargetProperty="(UIElement.RenderTransform).(TransformGroup.Children)[3].(TranslateTransform.Y)">
                                            <SplineDoubleKeyFrame KeyTime="00:00:00" Value="0"/>
                                            <SplineDoubleKeyFrame KeyTime="00:00:00.800" Value="40"/>
                                        </DoubleAnimationUsingKeyFrames>
                                    </Storyboard>
                                </vsm:VisualState>
                                <vsm:VisualState x:Name="Normal">
                                    <Storyboard>
                                        <ObjectAnimationUsingKeyFrames BeginTime="00:00:00" Duration="00:00:00.0010000" 
Storyboard.TargetName="PreviousContentPresentationSite" Storyboard.TargetProperty="(UIElement.Visibility)">
                                            <DiscreteObjectKeyFrame KeyTime="00:00:00">
                                                <DiscreteObjectKeyFrame.Value>
                                                    <Visibility>Collapsed</Visibility>
                                                </DiscreteObjectKeyFrame.Value>
                                            </DiscreteObjectKeyFrame>
                                        </ObjectAnimationUsingKeyFrames>
                                    </Storyboard>
                                </vsm:VisualState>
                            </vsm:VisualStateGroup>
</vsm:VisualStateManager.VisualStateGroups>

 

This is how the sample is setup:

        <local:TransitioningControl x:Name="contentChanger" 
                                    HorizontalAlignment="Center" 
                                    VerticalAlignment="Center" 
                                    Content="{Binding Path=CurrentTime}">
            <local:TransitioningControl.ContentTemplate>
                <DataTemplate>
                    <Border BorderBrush="Blue" BorderThickness="2">
                        <ContentPresenter Content="{Binding}" />
                    </Border>
                </DataTemplate>
            </local:TransitioningControl.ContentTemplate>
</local:TransitioningControl>

CurrentTime is a string that changes each second. The datatemplate is not necessary at all, just showing that it works.

I’d appreciate some feedback on this. So let me know if you would find such a control useful.

Source

Thursday, November 20, 2008 1:40:00 AM (Romance Standard Time, UTC+01:00)  #    Comments [0]  |  Trackback
 Monday, November 17, 2008

In my previous post, I showed an approach to getting the expander to ‘reveal’ its content. Although it worked well, it did so by programmatically creating a storyboard and targeting the height/width. That did not sit well with me, and in this post I’ll show another approach.

Before I had settled on that approach, I had created a dependency property on Expander ‘ExpandAmount’ but quickly realized I could not target it from within the template. Since you also can not target custom attached properties in a storyboard, I was stuck. However, the solution is as clear as it is simple: Just create a custom ContentControl that has our beloved ‘ExpandAmount’ property. We will be able to target that property for sure!

The template becomes:

image

Note that I set the Height and Width to 0, because the expander always begins in ‘collapsed’ state. Visibility is not set to collapsed.
We can target it in our –now Blend friendly- VSM:

image

The relevant code on our ExpandableContentControl is shown here:

image

Since content can be either a UIElement or a regular object (like String), we can not use it to determine the size we would like to be. However, we can take a look at the contentpresenter that is presenting our content and ask it. By doing this in the OnApplyTemplate, we will not be able to respond to changes inside the contenttemplate. However, for our demonstration, that is not a big issue.

One issue I ran into was the fact that this control has no nice way to get to the expander and find out how it should do calculate its Size. Based on the ExpandDirection, we might want to change the width or the height. I chose to have the Expander tell its content when it starts to expand or collapse.

We end up with a fully customizable expander. Go have some fun in Blend. Below is a sample where I adjusted the speed to slow down at the end. Also included is the source.

 

Source

Monday, November 17, 2008 8:42:24 PM (Romance Standard Time, UTC+01:00)  #    Comments [7]  |  Trackback
 Friday, November 14, 2008

A few weeks ago, we released the Silverlight control toolkit. There are some nice controls in there, including the expander.
In this post I will take a look at the expander and alter is so it’s content transitions in/out. Yes, my peeps: we are extending the expander!

In a simple scenario, the expander is used as follows:

image

I have used a border to show off its content. When you run this program, and click on the expander, this is what you will see:

image

Now, when you click the expander button (helpfully pointed out by my big red arrow!), the expander will immediately remove our content. There is no nice effect or transition going on.
This is consistent with WPF behavior.

But it is not cool, is it?

So, let’s take a better look, and open up the expander.xaml inside of our toolkit source. The relevant part is here:

image

and in the visual states:

image

So, the ‘ExpandSite’ is an element that has a visibility of Collapsed. When the control is ‘expanded’ the expandsite will be set to visible. Effective, but no fun.

We will manually transition the height of the content. I will leave it up to you to create a switch on the expanddirection and animate the width instead.

In order to hook everything up, I will create a new class, called: SmoothExpander.

image

The code should point you to the next change: I have added a scrollviewer inside the new template. So, I copied the expander template and put it inside themes\generic.xaml. The relevant parts are:

image

and

image

First off, I removed the Storyboard in the VisualState, since I will be creating on in code. Also, I have put a scrollviewer around my contentcontrol. Let’s take a look at that in more detail:

  • The scrollviewer will host the contentcontrol
  • Its height is set to 0, so it will not show up
  • The visibility of the contentcontrol is set to Visible

This gives us the option of using the extentHeight property of the Scrollviewer to figure out how large our contentcontrol wants to be!

So, lets implement our behavior:

image

I have been wanting to show how to use a storyboard in code for some time.
In both cases a storyboard is created with a DoubleAnimation that animates from the current Height to either 0 or the intended height. We couple this animation to our scrollViewer and off we go.

Since we do not collapse or expand straight away, we should change our code to run those methods on the storyboard finish:

image

 

Running sample is below. The second expander is of type ‘SmoothExpander’.

Source for this sample

I kept the sample as straightforward as possible. Extra work that should be done though:

  1. Create a templatepart attribute so that Blend knows about this scrollviewer
  2. Do not rely on the existence of the scrollviewer.
  3. Use expanddirection to toggle behavior.
  4. Possibly allow for a maximum to be set on the scrollviewer.
Friday, November 14, 2008 2:15:12 AM (Romance Standard Time, UTC+01:00)  #    Comments [2]  |  Trackback
 Tuesday, October 28, 2008

[update: source included
I have included the source. The source code works with the asp.net developer webserver instead of IIS. That will make it easier for you to run the sample!]

The autocomplete box is a control that will popup a combobox based on what the user is typing. You see a lot of these on the web, and for good reason: they really help make your enduser more productive!
In this post I will walk you through building a (WCF) webservice and connecting it up to the box. As you will see, it's really easy.

If you are unable to visualize it, this picture from our codeplex pages demonstrates a simple example:

AutoCompleteBox example

Step 1: create the webservice

We start of by creating a new project and choosing to create a new asp.net project for us. It will hold our webservice. Let's add one by choosing the 'Silverlight enabled WCF Service' template that should be when you add a new item.

[sample uses asp.net web server]

Setting everything up correctly:
You will need to install IIS and most of the (non-default) settings. Visual studio will complain if you are not setup correctly and tell you which features it lacks.
What bites many people that install IIS after they install .Net is that WCF isn't registered correctly. You can do so by issuing the 'ServiceModelReg /r' command. Find it here: %SystemRoot%\Microsoft.Net\Framework\v3.0\Windows Communication Foundation\

Do run visual studio as an administrator. You will get an error saying that the client can not be found, if you don't.

Lastly: you will not need any crossdomain xml definitions because you are running from the asp.net server. Do not forget to add one, if you are publishing your project though!!

So, add a service called 'NamesService.svc'.
After you have made a list of your friends, return them in a similar fashion as I do:

image

(Actually, after watching the PDC keynotes, don't you really want to use livemesh to do this kinds of stuff?? Or could we get this from Bluehoo? )

Step 2: connecting from silverlight

In your silverlight application, add a service reference. Because you are working from one big happy solution, you will be able to press the discover button and get your service. Add it and give it a nice name.

image

This will add a proxy to your Silverlight client.

Intermezzo:
People that have read this blog for some time, know I'm not very impressed with all the autogenerated proxy features. I do not feel generating your proxy gives you enough power over what you are sending over the wire. Also, I might want to do crazy stuff in my proxy. However, Silverlights data capabilities are pretty restricted and I will admit that the VS team has done some great integration here. All in all, this runs out of the box without any hickups.

Read my articles about EntityFramework Poco implementations which does a lot of WCF customization if you want to know more.

Step 2.1: test it out, man!

Let's just quickly see if we can get our service to work! Add these lines of code in your page.xaml.cs file and run.

image

This should print out the number 7 in your output.

We are first setting up a callback that will be called when the webservice returns with information. The args.Result property is a strongly typed property which holds your result.
We kick of the call to the webservice with our proxy.GetFriendsAsync() call.

Step 3: bring in the autocomplete textbox

It is time to add the autocomplete textbox in the mix.

Add a reference to the silverlight toolkit to your project, and (do) use the 'controls' prefix as the xmlns namespace. Then, just add it to the page xaml.

image

I have added the box and have added an eventhandler to the Populating event. This will allow me intercept and call our webservice. Dropping back to code, we'll hook everything up:

image

On line 33, I'm cancelling the population. Populating should really be done synchronously and fast. Since we can't do it fast, we will be doing the populating ourselves.
On line 50, we make the call to our webservice. When it is finished, it will execute the anonymous delegate on lines 39 to 46.
Our args.Result is a string[] with friends from the server. I will combine that with a list of friends we might keep in a different source.

The result:

image

 

Check out the LiveSearch scenario sample that comes with the toolkit. It connects to livesearch and fetches search suggestions on the fly.

AutoCompleteBoxToWebserviceProject.zip (159.35 KB)
Tuesday, October 28, 2008 11:33:41 PM (Romance Standard Time, UTC+01:00)  #    Comments [4]  |  Trackback