Skip to main content Skip to footer

ComponentOne Toolbar for Silverlight Samples

Overview

This article demonstrates simple techniques that can be used to customize the behavior and appearance of the C1Toolbar control for Silverlight. The techniques presented take advantage of the Silverlight (and WPF) compositional nature, which allows developers and designers to combine controls and layout elements with great flexibility. Specifically, the article demonstrates how you can accomplish the following:

  1. Create compound toolbar buttons that look like regular buttons, but have several clickable areas
  2. Create dropdown elements that use a Grid element to provide a rich layout
  3. Create dropdown elements that appear when the mouse hovers over them, rather than only when the user clicks them

These items are not artificial samples designed to show off unique features of the C1Toolbar. They came from real customers who wanted to implement these features in real applications.

1. Creating Compound Buttons

To create groups of buttons in a C1Toolbar control, you would normally use XAML such as this:


<c1tb:C1ToolbarStrip >  
    <c1tb:C1ToolbarButton Content="Button1" />  
    <c1tb:C1ToolbarButton Content="Button2" />  
    <c1tb:C1ToolbarButton Content="Button3" />  
</c1tb:C1ToolbarStrip>  

Which would create a toolbar that looks like this: Notice how the edges of the buttons are automatically adjusted so the first and last buttons have rounded corners and the middle one does not. Now suppose the application designer wanted to combine the three buttons into a single one. The desired result is something that looks like a regular button, but has three separate clickable areas. Thanks to the compositional nature of Silverlight, this turns out to be extremely easy. We can simply add a StackPanel as the content of a C1ToolbarButton, place the inner buttons in the toolbar, and remove the borders of the buttons:


  <c1tb:C1ToolbarStrip >  
    <c1tb:C1ToolbarButton >  
      <StackPanel Orientation="Horizontal" >  
        <Button Content="Area 1" IsTabStop="False" BorderThickness="0" />  
        <Button Content="Area 2" IsTabStop="False" BorderThickness="0" />  
        <Button Content="Area 3" IsTabStop="False" BorderThickness="0" />  
      </StackPanel>  
    </c1tb:C1ToolbarButton>  
  </c1tb:C1ToolbarStrip>  

The result is very close to what we want: Unfortunately, even though we specified zero for the BorderThickness, there is still a white gap between the buttons (you have to look carefully to see it). The gap appears because of the template used to build the regular Button objects. The template contains a Grid panel named "Background" that has a margin which is not affected by the BorderThickness property. The easiest way to eliminate the undesired gap is to create a new button class that derives from Button and override the OnApplyTemplate method to modify the template:


public class InnerButton : Button  
  {  
    public override void OnApplyTemplate()  
    {  
      base.OnApplyTemplate();  
      Grid grid = ((Border)GetTemplateChild("Background")).Child as Grid;  
      if (grid != null)  
      {  
        grid.Margin = new Thickness(0);  
      }  
    }  
  }  

The code is quite simple, but it does assume you know how the base class template is structured. We can now use our new InnerButton class as follows:


<c1tb:C1ToolbarStrip >  
    <c1tb:C1ToolbarButton >  
      <StackPanel Orientation="Horizontal" >  
        <local:InnerButton Content="Area 1" IsTabStop="False" BorderThickness="0" />  
        <local:InnerButton Content="Area 2" IsTabStop="False" BorderThickness="0" />  
        <local:InnerButton Content="Area 3" IsTabStop="False" BorderThickness="0" />  
      </StackPanel>  
    </c1tb:C1ToolbarButton>  
  </c1tb:C1ToolbarStrip>  

And this time the result is exactly what we wanted: a single button with three clickable areas: Even though the result looks like a single button, there are three buttons in the toolbar. Each InnerButton provides visual feedback when the mouse hovers over it, and each has its own Click event. Remember our InnerButton class inherits from the regular Button, so you are not limited to showing text in each button. You can also use images or any other element your application requires.

2. Using Grid elements as layout elements for dropdowns

In addition to buttons, many toolbars contain dropdown items similar to menus. These are implemented using the C1ToolbarDropDown class. For example, you could create a simple dropdown using this XAML:


<c1tb:C1ToolbarStrip >  
    <c1tb:C1ToolbarDropDown Padding="3" Header="Regular DropDown">  
      <c1tb:C1ToolbarDropDown.Menu>  
        <c1:C1ContextMenu>  
          <c1:C1MenuItem Header="Item1" />  
          <c1:C1MenuItem Header="Item2" />  
          <c1:C1MenuItem Header="Item3" />  
        </c1:C1ContextMenu>  
      </c1tb:C1ToolbarDropDown.Menu>  
    </c1tb:C1ToolbarDropDown>  
  </c1tb:C1ToolbarStrip>  

Which would create the following: But you are not restricted to simple menus. In addition to the Menu property used in the example above, the C1ToolbarDropDown has a Content property that allows you to use any element as a dropdown. The most flexible option is to use a Grid panel, which provides virtually unlimited layout options. For example, imagine that instead of a simple menu we wanted to display a list of items consisting of some left-aligned text and a right aligned button. This can be accomplished using this XAML:


<c1tb:C1ToolbarStrip >  
  <c1tb:C1ToolbarDropDown Padding="3" Header="Grid in DropDown">  
    <Grid>  
      <Grid.ColumnDefinitions>  
        <ColumnDefinition Width="200"/>  
        <ColumnDefinition Width="100"/>  
      </Grid.ColumnDefinitions>  
      <Grid.RowDefinitions>  
        <RowDefinition />  
        <RowDefinition />  
        <RowDefinition />  
      </Grid.RowDefinitions>  
      <TextBlock VerticalAlignment="Center" Text="Label 1" />  
      <Button Content="Button 1" Grid.Column="1" />  
      <TextBlock VerticalAlignment="Center" Text="Label 2" Grid.Row="1" />  
      <Button Content="Button 2" Grid.Row="1" Grid.Column="1" />  
      <TextBlock VerticalAlignment="Center" Text="Label 3" Grid.Row="2" />  
      <Button Content="Button 3" Grid.Row="2" Grid.Column="1" />  
    </Grid>  
  </c1tb:C1ToolbarDropDown>  
</c1tb:C1ToolbarStrip>  

Which would create the following: The Grid panel is the most versatile panel available in Silverlight and in WPF. It can host any type of element and lay it out over single cells or cell spans, with the usual choices of vertical and horizontal alignment. You can leverage all this power directly from your C1ToolbarDropDown elements.

3. Creating dropdowns that appear on mouse hover

Many web pages have menus that dropdown when the mouse hovers over them (as opposed to when the user clicks them). To accomplish the same effect with a C1ToolbarDropDown, we need to do the following:

  1. Handle the MouseEnter event of the C1ToolbarDropDown to show the dropdown.
  2. Handle the MouseLeave event of the C1ToolbarDropDown and of the dropdown itself to start a timer that will close the dropdown. This is better than closing the dropdown immediately because it makes the UI more forgiving. Users can move the mouse away and back to the control without having the dropdown disappear if they make a slightly imprecise move.
  3. Handle the MouseEnter event of the C1ToolbarDropDown and of the dropdown itself to stop the timer and prevent the dropdown from closing if the user moves the mouse back over the dropdown within a short time.

As you can see, showing the dropdown is easy. Hiding it at the right time is more difficult. To accomplish this, we define a HoverDropDown class that looks like this:


public class HoverDropDown : C1ToolbarDropDown  
  {  
    Popup _helper;  
    Rectangle _rect;  
    DispatcherTimer _timer;  

    public HoverDropDown()  
    {  
      // create timer to hide the dropdown when the mouse moves away  
      // from the toolbar item itself and from the dropdown  
      _timer = new DispatcherTimer();  
      _timer.Interval = TimeSpan.FromSeconds(0.5);  
      \_timer.Tick += \_timer_Tick;  

      // create transparent element to detect mouse movement over the  
      // toolbar item  
      _rect = new Rectangle();  
      _rect.Fill = new SolidColorBrush(Colors.Transparent);  
      _helper = new Popup();  
      \_helper.Child = \_rect;  

      // start/stop the timer when mouse leaves/enters the toolbar item  
      \_rect.MouseLeave += (s, e) => \_timer.Start();  
      \_rect.MouseEnter += (s, e) => \_timer.Stop();  
    }  

The HoverDropDown class derives from C1ToolbarDropDown and defines a timer to hide the dropdown as described above. In addition to the timer, the class defines a Rectangle object that will be used to detect mouse movements over the control itself (regardless of its contents). When the mouse leaves the Rectangle, the timer starts ticking. If the mouse returns to the control before the specified delay has elapsed, the timer stops before the dropdown closes. Next, we need to attach similar event handlers to the dropdown element itself. This can be done using the OnApplyTemplate method as shown below:


 // connect our timer to the dropdown element (popup child)  
  public override void OnApplyTemplate()  
  {  
    // allow default handling  
    base.OnApplyTemplate();  

    // get the dropdown element from the template  
    var popup = GetTemplateChild("DropDownPopup") as Popup;  
    var popupChild = (FrameworkElement)popup.Child;  

    // start/stop the timer when mouse leaves/enters the dropdown  
    popupChild.MouseLeave += (s, e) => _timer.Start();  
    popupChild.MouseEnter += (s, e) => _timer.Stop();  
  }  

Next, we need to override the OnMouseEnter method to show the dropdown and to position the Rectangle element declared earlier so it will detect mouse movements while the dropdown is visible:


 // drop down when mouse enters the toolbar item  
  // control, setup to detect when the mouse leaves the dropdown element  
  protected override void OnMouseEnter(MouseEventArgs e)  
  {  
    // allow default handling  
    base.OnMouseEnter(e);  

    if (!IsDropDownOpen)  
    {  
      // drop down now  
      IsDropDownOpen = true;  

      // update mouse detector position  
      Point pt = TransformToVisual(null).Transform(new Point());  
      _rect.Width = ActualWidth;  
      _rect.Height = ActualHeight;  
      _helper.HorizontalOffset = pt.X;  
      _helper.VerticalOffset = pt.Y;  
      _helper.IsOpen = true;  
    }  
  }  

And finally, we need to implement the timer's Tick event handler which will close the dropdown half a second after the mouse has left the control or the dropdown:


 // close popup 0.5 seconds after the mouse leaves the popup area  
  void \_timer\_Tick(object sender, EventArgs e)  
  {  
    IsDropDownOpen = _helper.IsOpen = false;  
  }  

That concludes the implementation of the HoverDropDown class. You can use it in XAML exactly as you would use the base C1ToolbarDropDown class:


<c1tb:C1ToolbarStrip >  
    <local:HoverDropDown Padding="3" Header="Hover">  
      <StackPanel Orientation="Vertical">  
        <CheckBox Content="Check box 1" />  
        <CheckBox Content="Check box 2" />  
        <CheckBox Content="Check box 3" />  
      </StackPanel>  
    </local:HoverDropDown>  
  </c1tb:C1ToolbarStrip>  

Showing an image would not help illustrate the customized behavior. To see the HoverDropDown in action, please build and run the sample included with the article.

Downloads

<c1tb:C1ToolbarStrip > <c1tb:C1ToolbarButton Content="Button1" /> <c1tb:C1ToolbarButton Content="Button2" /> <c1tb:C1ToolbarButton Content="Button3" /> </c1tb:C1ToolbarStrip>

MESCIUS inc.

comments powered by Disqus