I wanted to build a toolbar where toolbar button expands when you hover over it.

 I will not only admit that I am not a designer, but also admit that I really suck at it when it comes to picking colors :) but that is why I wanted to build toolbar in such a way so that somebody can pick up this code and make it look better by just changing the XAML.

So to build a Toolbar, I needed to build two custom controls...

  1. Toolbar: This is just a container class. This class contains buttons that are added to toolbar.
  2. ToolbarButton: This is actual button class that implements the behavior for the button part as well as the visual animation part.

Toolbar code is straight forward. Since it just a container its XAML just contains a canvas.

<Canvas xmlns="http://schemas.microsoft.com/client/2007"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Width="640"
        Height="480" />

The only thing interesting about this class is actual children Collection. Since in Silverlight there is no collection class that actually fires event when an item is added (something like ObservableCollection), you need to implement one. Code here is courtasy Peter Blois. Link to code that Peter gave me are here.  

Only interesting part of the rest of the code is where layout is updated when new item is added to toolbarcollection.

public Toolbar()
{
    System.IO.Stream s = this.GetType().Assembly.GetManifestResourceStream("Toolbar.Toolbar.xaml");
    _rootCanvas = this.InitializeFromXaml(new System.IO.StreamReader(s).ReadToEnd()) as Canvas;
    _toobarcollection = new ObservableCollection<ToolbarButton>();
    _toobarcollection.CollectionChanged += new NotifyCollectionChangedEventHandler<ToolbarButton>(_toobarcollection_CollectionChanged);
    this.Loaded += new EventHandler(Toolbar_Loaded);
}

void _toobarcollection_CollectionChanged(object sender, NotifyCollectionChangedEventArgs<ToolbarButton> e)
{
    UpdateLayout();
}

public ObservableCollection<ToolbarButton> ToolBarCollection
{
    get { return _toobarcollection; }
}

 

Now, the ToolbarButton control...

This one has some specific behavior...

  1. From OM perspective Button has three APIs...
    1. Button has tooltip (of type string) that shows up when mouse enters the button
    2. Button has Image property (of type Uri) where consumer of the control can specify the image to be displayed in the button
    3. Button has a Click event which user can hook into
  2. I wanted to Popup animation when mouse enters the button
  3. I wanted to Popdown animation when mouse leaves the button
  4. I also wanted default look of the button to change when the button is pressed.

I wanted to make sure somebody can change the Popup and Popdown as well as pressed/unpressed look of the button later.

Xaml for ToolbarButton contains its default look. It also contains two storyboards called (popup and popdown) in the code, I only refer to those, that means somebody can change the contents of those storyboard and make them look good.

 

<Canvas.Resources>
        
        
                <Storyboard x:Name="Popup">
                    <DoubleAnimationUsingKeyFrames BeginTime="00:00:00" Storyboard.TargetName="rectangle" Storyboard.TargetProperty="(UIElement.RenderTransform).(TransformGroup.Children)[0].(ScaleTransform.ScaleX)">
                        <SplineDoubleKeyFrame KeyTime="00:00:00.2000000" Value="2"/>
                    </DoubleAnimationUsingKeyFrames>
                    <DoubleAnimationUsingKeyFrames BeginTime="00:00:00" Storyboard.TargetName="rectangle" Storyboard.TargetProperty="(UIElement.RenderTransform).(TransformGroup.Children)[0].(ScaleTransform.ScaleY)">
                        <SplineDoubleKeyFrame KeyTime="00:00:00.2000000" Value="2"/>
                    </DoubleAnimationUsingKeyFrames>
                    <DoubleAnimationUsingKeyFrames BeginTime="00:00:00" Storyboard.TargetName="image" Storyboard.TargetProperty="(UIElement.RenderTransform).(TransformGroup.Children)[0].(ScaleTransform.ScaleX)">
                        <SplineDoubleKeyFrame KeyTime="00:00:00.2000000" Value="2"/>
                    </DoubleAnimationUsingKeyFrames>
                    <DoubleAnimationUsingKeyFrames BeginTime="00:00:00" Storyboard.TargetName="image" Storyboard.TargetProperty="(UIElement.RenderTransform).(TransformGroup.Children)[0].(ScaleTransform.ScaleY)">
                        <SplineDoubleKeyFrame KeyTime="00:00:00.2000000" Value="2"/>
                    </DoubleAnimationUsingKeyFrames>
                    <DoubleAnimationUsingKeyFrames BeginTime="00:00:00" Storyboard.TargetName="image" Storyboard.TargetProperty="(UIElement.Opacity)">
                        <SplineDoubleKeyFrame KeyTime="00:00:00.2000000" Value="1"/>
                    </DoubleAnimationUsingKeyFrames>
                </Storyboard>
                <Storyboard x:Name="PopDown">
                    <DoubleAnimationUsingKeyFrames BeginTime="00:00:00" Storyboard.TargetName="rectangle" Storyboard.TargetProperty="(UIElement.RenderTransform).(TransformGroup.Children)[0].(ScaleTransform.ScaleX)">
                        <SplineDoubleKeyFrame KeyTime="00:00:00" Value="2"/>
                        <SplineDoubleKeyFrame KeyTime="00:00:00.3000000" Value="1"/>
                    </DoubleAnimationUsingKeyFrames>
                    <DoubleAnimationUsingKeyFrames BeginTime="00:00:00" Storyboard.TargetName="rectangle" Storyboard.TargetProperty="(UIElement.RenderTransform).(TransformGroup.Children)[0].(ScaleTransform.ScaleY)">
                        <SplineDoubleKeyFrame KeyTime="00:00:00" Value="2"/>
                        <SplineDoubleKeyFrame KeyTime="00:00:00.3000000" Value="1"/>
                    </DoubleAnimationUsingKeyFrames>
                    <DoubleAnimationUsingKeyFrames BeginTime="00:00:00" Storyboard.TargetName="image" Storyboard.TargetProperty="(UIElement.RenderTransform).(TransformGroup.Children)[0].(ScaleTransform.ScaleX)">
                        <SplineDoubleKeyFrame KeyTime="00:00:00" Value="2"/>
                        <SplineDoubleKeyFrame KeyTime="00:00:00.3000000" Value="1"/>
                    </DoubleAnimationUsingKeyFrames>
                    <DoubleAnimationUsingKeyFrames BeginTime="00:00:00" Storyboard.TargetName="image" Storyboard.TargetProperty="(UIElement.RenderTransform).(TransformGroup.Children)[0].(ScaleTransform.ScaleY)">
                        <SplineDoubleKeyFrame KeyTime="00:00:00" Value="2"/>
                        <SplineDoubleKeyFrame KeyTime="00:00:00.3000000" Value="1"/>
                    </DoubleAnimationUsingKeyFrames>
                    <DoubleAnimationUsingKeyFrames BeginTime="00:00:00" Storyboard.TargetName="image" Storyboard.TargetProperty="(UIElement.Opacity)">
                        <SplineDoubleKeyFrame KeyTime="00:00:00" Value="1"/>
                        <SplineDoubleKeyFrame KeyTime="00:00:00.3000000" Value="0.7"/>
                    </DoubleAnimationUsingKeyFrames>
                    <ColorAnimationUsingKeyFrames BeginTime="00:00:00" Storyboard.TargetName="image" Storyboard.TargetProperty="(UIElement.OpacityMask).(SolidColorBrush.Color)">
                        <SplineColorKeyFrame KeyTime="00:00:00" Value="#FF000000"/>
                        <SplineColorKeyFrame KeyTime="00:00:00.3000000" Value="#FFEFF4FA"/>
                    </ColorAnimationUsingKeyFrames>
                </Storyboard>
        
    </Canvas.Resources>

 

 I calls the Begin on those storyboard when mouse enters....

 

void _rootCanvas_MouseLeave(object sender, EventArgs e)
        {
            _popdown.Begin();
            this.SetValue(Canvas.ZIndexProperty, 0);
            _mousedown = false;
            UnPressedLook();
            _tooltip.Visibility = Visibility.Hidden;
        }

        void _rootCanvas_MouseEnter(object sender, MouseEventArgs e)
        {
            _popup.Begin();
            this.SetValue(Canvas.ZIndexProperty, 5);
            _tooltip.Visibility = Visibility.Visible;
        }

For the pressed look, i create a seperate xaml that contains the brush that defines the pressed look.

 

<LinearGradientBrush EndPoint="1,0.5" StartPoint="0,0.5">
  <GradientStop Color="#FF6392C7" Offset="0.125"/>
  <GradientStop Color="#FF6392C7" Offset="0.88"/>
  <GradientStop Color="#FFF8F2F2" Offset="0"/>
  <GradientStop Color="#FFF8F2F2" Offset="1"/>
</LinearGradientBrush>

In the code, this xaml can be read using XamlReader.Load and you can swap the looks when mouse button is down or up.

 

void _rootCanvas_MouseLeftButtonDown(object sender, MouseEventArgs e)
        {
            _mousedown = true;
            PressedLook();
        }

        void _rootCanvas_MouseLeftButtonUp(object sender, MouseEventArgs e)
        {
            if (this.Click != null && _mousedown == true)
            {
                this.Click(this, EventArgs.Empty);
                _mousedown = false;
            }
            UnPressedLook();

        }

The complete code for ToolBar is here.