Tuesday, December 9, 2008

Tutor.com Classroom How-To #11: Build a Silverlight ToolbarButton user control

These How-To tips are taken from The Tutor.com Classroom: Architecture and techniques using Silverlight, WPF, and the Microsoft .NET Framework.

You can check out the Silverlight Tutor.com Classroom in "practice" mode, although the real experience is with a live tutor on the other side!

One of the controls missing out-of-the gate from Silverlight is the Toolbar. There is no magic to the WPF Toolbar (it’s just a control container that auto-docks itself and auto-styles its children), but for free you get the flat button look-and-feel that users expect at the top of an application.

In Silverlight, we have to manually create this look-and-feel. Since our application’s toolbar consists solely of buttons (both normal and toggle buttons), and since the buttons in our toolbar have the “icon followed by a text description” format, we can abstract this to build the ToolbarButton user control.

Begin by creating a new user control. Define the styles for the Hover and Pressed states in your user control’s Xaml resources:

<UserControl.Resources>
<LinearGradientBrush x:Key="HoverBrush" StartPoint="0,0" EndPoint="0,1">
<LinearGradientBrush.GradientStops>
<GradientStop Color="White" />
<GradientStop Color="#F8D28F" Offset=".3" />
</LinearGradientBrush.GradientStops>
</LinearGradientBrush>

<LinearGradientBrush x:Key="PressedBrush" StartPoint="0,0" EndPoint="0,1">
<LinearGradientBrush.GradientStops>
<GradientStop Color="#F8D28F"/>
<GradientStop Color="White" Offset=".3" />
</LinearGradientBrush.GradientStops>
</LinearGradientBrush>
</UserControl.Resources>

Then, use a Grid to hold two Border objects; one contains the image and the text label, and the other is a half-opaque, white filled Border that we use when the button is disabled.

<Grid>
<Border x:Name="MainBorder" BorderThickness="1" CornerRadius="1" MouseEnter="Border_MouseEnter" MouseLeave="Border_MouseLeave" MouseLeftButtonUp="Border_MouseLeftButtonUp" Cursor="Hand" Margin="1">
<StackPanel Orientation="Horizontal" Margin="5,1,5,1">
<Image x:Name="BorderImage" Width="16" />
<TextBlock x:Name="BorderTextBlock" Style="{StaticResource blackText}" Margin="5" />
</StackPanel>
</Border>
<Border Background="#B2FFFFFF" x:Name="DisabledBackground" BorderThickness="1" CornerRadius="2" BorderBrush="#B2FFFFFF" Visibility="Collapsed" />
</Grid>

Next, wire up the IsEnabled, ToolTip, ImageSource, and Caption properties, and implement the hover and click functionality. Note that these properties are backed by dependency properties on the objects we defined in our Xaml.

public partial class ToolbarButton : UserControl
{
bool m_IsEnabled = true;

public new bool IsEnabled
{
set
{
MainBorder.Background = null;
MainBorder.BorderBrush = null;
DisabledBackground.Visibility = (value) ? Visibility.Collapsed : Visibility.Visible;
}
}

public string ToolTip
{
get { return ToolTipService.GetToolTip(MainBorder).ToString();}
set { ToolTipService.SetToolTip(MainBorder, value);}
}

public ImageSource ImageSource
{
get { return BorderImage.Source;}
set { BorderImage.Source = value;}
}

public string Caption
{
get { return BorderTextBlock.Text;}
set { BorderTextBlock.Text = value;}
}

public bool IsToggleButton { get; set; }
public bool IsToggled { get; set; }

public event MouseButtonEventHandler Clicked;

public ToolbarButton()
{
InitializeComponent();
}

private void Border_MouseEnter(object sender, MouseEventArgs e)
{
//if we're a toggle button and we're on...
if (this.IsToggleButton && this.IsToggled)
return;

Border b = sender as Border;

b.Background = Resources["HoverBrush"] as Brush;
b.BorderBrush = new SolidColorBrush(ColorHelper.FromHexString("FFCA8103"));
}

private void Border_MouseLeave(object sender, MouseEventArgs e)
{
//if we're a toggle button and we're on...
if (this.IsToggleButton && this.IsToggled)
return;

Border b = sender as Border;

b.Background = null;
b.BorderBrush = null;
}

private void Border_MouseLeftButtonUp(object sender, MouseButtonEventArgs e)
{
if (!m_IsEnabled)
return;

if (this.IsToggleButton)
{
this.IsToggled = !this.IsToggled;

//set state
Border b = sender as Border;
b.Background = (this.IsToggled) ? Resources["PressedBrush"] as Brush : Resources["HoverBrush"] as Brush;
}

if (this.Clicked != null)
this.Clicked(sender, e);
}
}

Finally, in your container Xaml, use the ToolbarButton class inside a StackPanel with Orientation set to Horizontal:

<StackPanel Orientation="Horizontal">
<local:ToolbarButton ImageSource="/Resources/board.png" Caption="Add Whiteboard" Clicked="AddWhiteboard_Click" Margin="1" />
...
</StackPanel>

No comments: