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>

Friday, December 5, 2008

default DateTime parameters to an old date, never NULL

you want to use:

CREATE PROCEDURE …
@StartDate = ‘1/1/2000’

not

CREATE PROCEDURE …
@StartDate = NULL

the reason for this is that if you use NULL, you have to put WHERE (@StartDate IS NULL OR DateValue > @StartDate) in your where clause, which screws your query optimization. if you default to an old date, you can just put WHERE (DateValue > @StartDate) so the optimizer will be happy—

beware API overloads

on business object methods like Consumer.UpdateConsumer() we have several overloads that let you update only a few specific Consumer properties. for example, there’s one overload that takes a CustomerStatusId, another that takes a WhereHeard and a CustomerStatusId, etc. these overloads all call a common, private updateConsumer() function that takes care of the database update, and they pass in the object’s properties for the fields you are not updating, so that the original values are preserved.

however, this means that if you call an overload that takes multiple parameters, any value you pass in as a parameter will be used for the database update. so passing in “null” or empty string will update the database with “null” or empty string for that field, which may not be your intention--

Thursday, December 4, 2008

beware maxBufferSize, maxReceivedMessageSize, and maxStringCotentLength when adding service reference in VS2008

when you add a service reference in VS2008, you end up with a entry in your applications app.config file. this element contains binding elements for each service you add.

BEWARE: the default value for three critical attributes, maxBufferSize, maxReceivedMessageSize, and maxStringCotentLength, is set considerably low (64KB). we have hit this problem more than once now with deployed software, and unfortunately the only way to correct it is to adjust these values in app.config and re-distribute the software. so if you think there is even the slightest chance that your service will be returning more than 64KB in data, be sure to adjust these values after you add the service reference.