Custom controls separate the logic from the visual appearance. We use a custom control when we want a strong separation between visual appearance and logic. By doing this, designers can modify the visual appearance without accessing or modifying the code.
Silverlight enables us to customize controls in several ways. We can customize most controls by modifying their default template. The two ways by which we can modify the appearance of a custom control are by using styles and by modifying the templates.
Styles
Styles are basically a collection of property settings. Using styles is a way by which you can easily change the visual characteristics of a control.
Templates
A template is a XAML file that defines the visual appearance and visual behavior of a control. You can completely replace the appearance of the control by modifying or replacing its default ControlTemplate. You can modify the ControlTemplate to add, rearrange, or delete the parts of a control.
Figure
The button above on the right side is a skinnable Button control.
Control Contract
Custom controls have a strict separation between the logic and visuals. While this strict separation has benefits, such as customizing the visuals without affecting the logic, it can be challenging for control designers to know what elements the control needs. This is achieved though the control contract. A control contract is an agreement between the logical and the visual parts of the control. In general, a control contract consists of:
- Visual properties exposed on the control class.
- Expected parts in the template.
- Expected logic associated with the parts in the template.
Parts and States Model
Silverlight implements the control contract through a parts and states model. The parts and states model keeps a separation between logic and visuals, while still maintaining an explicit control contract between the designer and the developer. The main concepts in the parts and states model are parts, states, state groups and visual transitions.
Parts
Parts are named elements inside the control template. The control logic expects these parts to be in the control template.
States
Visual states are the way the control appears in a particular state. For example, the following illustration shows how a button has a different background color in the Normal, MouseOver and Pressed states.
Figure
Customizing an Existing Control by using Control Template
A ControlTemplate specifies the visual structure and visual behavior of a control. You can customize the appearance of a control by giving it a new ControlTemplate. When you create a ControlTemplate, you replace the appearance of an existing control without changing its functionality. For example, you can make the buttons in your application round rather than the default square shape, but the button will still raise the Click event.
Controls have many properties, such as Background, Foreground, and FontFamily, that you can set to specify different aspects of the control's appearance, but the changes that you can make by setting these properties are limited. For example, you can set the Foreground property to blue and FontStyle to italic on a CheckBox.
Without the ability to create a new ControlTemplate for controls, all controls in every Silverlight-based application would have the same general appearance, which would limit the ability to create an application with a custom look and feel.
By default, every CheckBox has similar characteristics. For example, the content of the CheckBox is always to the right of the selection indicator, and the check mark is always used to indicate that the CheckBox is selected.
You create a ControlTemplate when you want to customize the control's appearance beyond what setting the other properties on the control will do. In the example of the CheckBox, suppose that you want the content of the check box to be above the selection indicator and an X to indicate that the CheckBox is selected. You specify this is the ControlTemplate of the CheckBox.
ControlTemplate in XAML
The Template property specifies the ControlTemplate of a control. Like many properties, the Template property can be set in the following ways:
- Locally set Template to a ControlTemplate that is defined inline.
- Locally set Template to a reference to a ControlTemplate that is defined as a resource.
- Set Template and define a ControlTemplate in a Style.
The following example shows setting the template locally and defining inline.
<Button VerticalAlignment="Top" Content="Click Me !"
Margin="261,72,279,0" Height="24" >
<Button.Template>
<ControlTemplate TargetType="Button">
<!--Define the ControlTemplate here.-->
</ControlTemplate>
</Button.Template>
</Button>
Listing
The following example shows defining ControlTemplate as resource and setting the Template to a reference to the resource.
<Grid.Resources>
<ControlTemplate TargetType="Button" x:Key="MyButtonTemplate">
<!--Define the ControlTemplate here.-->
</ControlTemplate>
</Grid.Resources>
<Button VerticalAlignment="Top" Content="Click Me !"
Margin="261,72,279,0" Height="24"
Style="{StaticResource MyButtonTemplate}">
</Button>
Listing
The following example demonstrates setting the Template property and defining the ControlTemplate in Style.
<Grid.Resources>
<Style TargetType="Button" x:Key="MyButtonTemplate">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="Button">
<!--Define the ControlTemplate here.-->
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</Grid.Resources>
<Button VerticalAlignment="Top" Content="Click Me !"
Margin="261,72,279,0" Height="24"
Style="{StaticResource MyButtonTemplate}">
</Button>
Listing
Changing the Appearance of a Control Depending on Its State
The difference between a button with its default appearance and the button in the preceding example is that the default button subtly changes when it is in different states. For example, the default button's appearance changes when the button is pressed, or when the mouse pointer is over the button. Although the ControlTemplate does not change the functionality of a control, it does change the control's visual behavior. A visual behavior describes the control appearance when it is in a certain state. To understand the difference between the functionality and visual behavior of a control, consider the button example. The button's functionality is to raise the Click event when it is clicked, but the button's visual behavior is to change its appearance when it is pointed to or pressed.
We use VisualState objects to specify the appearance of a control when it is in a certain state. A VisualState contains a Storyboard that changes the appearance of the elements that are in the ControlTemplate. You do not have to write any code to make this occur because the control's logic changes state by using the VisualStateManager. When the control enters the state that is specified by the VisualState.Name property, the storyboard begins. When the control exits the state, the Storyboard stops.
The following example shows the VisualState that changes the appearance of a Button when the mouse pointer is over it. The Name of the VisualState matches the name specified by the TemplateVisualStateAttribute on the Button class. The Storyboard changes the button's border color by changing the color of the BorderBrush. If you refer to the ControlTemplate example at the beginning of this topic, you will recall that BorderBrush is the name of the SolidColorBrush that is assigned to the Background of the Border.
<ControlTemplate TargetType="Button">
<Grid>
<vsm:VisualStateManager.VisualStateGroups>
<vsm:VisualStateGroup x:Name="CommonStates">
<vsm:VisualStateGroup.Transitions>
<vsm:VisualTransition GeneratedDuration="00:00:00.1" To="MouseOver"/>
<vsm:VisualTransition GeneratedDuration="00:00:00.1" To="Pressed"/>
<vsm:VisualTransition From="Normal"
GeneratedDuration="00:00:00.3000000"
To="MouseOver"/>
<vsm:VisualTransition From="MouseOver"
GeneratedDuration="00:00:00.5000000"
To="Normal"/>
<vsm:VisualTransition From="Pressed"
GeneratedDuration="00:00:00.5000000"
To="MouseOver"/>
</vsm:VisualStateGroup.Transitions>
<vsm:VisualState x:Name="Normal"/>
<vsm:VisualState x:Name="MouseOver">
<Storyboard>
<DoubleAnimationUsingKeyFrames Storyboard.TargetName="Hover"
Storyboard.TargetProperty="Opacity">
<SplineDoubleKeyFrame KeyTime="0"Value="1"/>
</DoubleAnimationUsingKeyFrames>
</Storyboard>
</vsm:VisualState>
<vsm:VisualState x:Name="Pressed">
<Storyboard>
<DoubleAnimationUsingKeyFrames BeginTime="00:00:00"
Duration="00:00:00.0010000"
Storyboard.TargetName="Background"
Storyboard.TargetProperty="(UIElement.Opacity)">
<SplineDoubleKeyFrame KeyTime="00:00:00"
Value="0.7"/>
</DoubleAnimationUsingKeyFrames>
</Storyboard>
</vsm:VisualState>
<vsm:VisualState x:Name="Disabled">
<Storyboard>
<DoubleAnimationUsingKeyFrames
Storyboard.TargetName="DisabledVisualElement"
Storyboard.TargetProperty="(UIElement.Opacity)">
<SplineDoubleKeyFrame KeyTime="0"
Value="0.65"/>
</DoubleAnimationUsingKeyFrames>
</Storyboard>
</vsm:VisualState>
</vsm:VisualStateGroup>
<vsm:VisualStateGroup x:Name="FocusStates">
<vsm:VisualStateGroup.Transitions>
<vsm:VisualTransition From="Focused"
GeneratedDuration="00:00:00.5000000"
To="Unfocused"/>
<vsm:VisualTransition From="Unfocused"
GeneratedDuration="00:00:00.3000000"
To="Focused"/>
</vsm:VisualStateGroup.Transitions>
<vsm:VisualState x:Name="Focused">
<Storyboard>
<ObjectAnimationUsingKeyFrames Duration="0"
Storyboard.TargetName="FocusVisualElement"
Storyboard.TargetProperty="Visibility">
<DiscreteObjectKeyFrame KeyTime="0" Value="Visible" />
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</vsm:VisualState>
<vsm:VisualState x:Name="Unfocused"/>
</vsm:VisualStateGroup>
</vsm:VisualStateManager.VisualStateGroups>
<Border x:Name="Background"
Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}"
CornerRadius="2,2,2,2">
<Border x:Name="Hover"
Background="{StaticResource HoverBrushRed}"
CornerRadius="2,2,2,2"
Height="Auto"
Width="Auto"
Opacity="0"/>
</Border>
<ContentPresenter HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
Margin="{TemplateBinding Padding}"
x:Name="contentPresenter" VerticalAlignment="{TemplateBinding VerticalContentAlignment}"
Content="{TemplateBinding Content}"
ContentTemplate="{TemplateBinding ContentTemplate}"/>
<Border x:Name="DisabledVisualElement"
IsHitTestVisible="false"
Opacity="0"
Background="{StaticResource DisabledBackgroundBrushRed}"
CornerRadius="2,2,2,2"/>
<Border x:Name="FocusVisualElement"
IsHitTestVisible="false"
Visibility="Collapsed"
BorderBrush="{StaticResource HoverBrushRed}"
BorderThickness="2,2,2,2"
CornerRadius="2,2,2,2"/>
</Grid></ControlTemplate>
Listing
Specifying the Duration of a Transition
You can specify how long a transition takes by setting the GeneratedDuration property. The preceding example has a VisualState that specifies that the button's border becomes transparent when the button is pressed, but the animation takes too long to be noticeable if the button is quickly pressed and released. You can use a VisualTransition to specify the amount of time it takes the control to transition into the pressed state. As given in above the listing above.
<vsm:VisualTransition GeneratedDuration="00:00:00.1"
To="MouseOver"/>
<vsm:VisualTransition GeneratedDuration="00:00:00.1"
To="Pressed"/>
Listing
Specifying Changes to the Control's Appearance during a Transition
The VisualTransition contains a Storyboard that begins when the control transitions between states. For example, you can specify that a certain animation occurs when the control transitions from the MouseOver state to the Normal State.
<Storyboard>
<DoubleAnimationUsingKeyFrames Storyboard.TargetName="Hover"
Storyboard.TargetProperty="Opacity">
<SplineDoubleKeyFrame KeyTime="0" Value="1"/>
</DoubleAnimationUsingKeyFrames>
</Storyboard>
Listing
Figure
The following XAML is the full reference of the Button displayed above.
<Color x:Key="NormalBrushGradientRed1">#FFF1BCBE</Color>
<Color x:Key="NormalBrushGradientRed2">#FFCE6865</Color>
<Color x:Key="NormalBrushGradientRed3">#FFAF231E</Color>
<Color x:Key="NormalBrushGradientRed4">#FF601818</Color>
<Color x:Key="NormalBorderBrushGradientRed1">#FFBBBBBB</Color>
<Color x:Key="NormalBorderBrushGradientRed2">#FF737373</Color>
<Color x:Key="NormalBorderBrushGradientRed3">#FF646464</Color>
<Color x:Key="NormalBorderBrushGradientRed4">#FF000000</Color>
<Color x:Key="SelectedBackgroundGradientRed1">#FFBBBBBB</Color>
<Color x:Key="SelectedBackgroundGradientRed2">#FF737373</Color>
<Color x:Key="SelectedBackgroundGradientRed3">#FF646464</Color>
<Color x:Key="SelectedBackgroundGradientRed4">#FFA1A1A1</Color>
<Color x:Key="SliderBorderGradientRed1">#FF3F3F3F</Color>
<Color x:Key="SliderBorderGradientRed2">#FFADADAD</Color>
<Color x:Key="ShadeBrushGradientRed1">#FF62676A</Color>
<Color x:Key="ShadeBrushGradientRed2">#FFD1D4D6</Color>
<Color x:Key="ShadeBrushGradientRed3">#FFFFFFFF</Color>
<LinearGradientBrush x:Key="NormalBrushRed" EndPoint="0.5,1" StartPoint="0.5,0">
<GradientStop Color="{StaticResource NormalBrushGradientRed1}" Offset="0" />
<GradientStop Color="{StaticResource NormalBrushGradientRed2}" Offset="0.41800001263618469" />
<GradientStop Color="{StaticResource NormalBrushGradientRed3}" Offset="0.418" />
<GradientStop Color="{StaticResource NormalBrushGradientRed4}" Offset="1" />
</LinearGradientBrush>
<LinearGradientBrush x:Key="NormalBorderBrushRed" EndPoint="0.5,1" StartPoint="0.5,0">
<GradientStop Color="{StaticResource NormalBorderBrushGradientRed1}" />
<GradientStop Color="{StaticResource NormalBorderBrushGradientRed2}" Offset="0.38" />
<GradientStop Color="{StaticResource NormalBorderBrushGradientRed3}" Offset="0.384" />
<GradientStop Color="{StaticResource NormalBorderBrushGradientRed4}" Offset="1" />
</LinearGradientBrush>
<RadialGradientBrush x:Key="HoverBrushRed">
<RadialGradientBrush.RelativeTransform>
<TransformGroup>
<ScaleTransform CenterX="0.5" CenterY="0.5" ScaleX="1.804" ScaleY="0.743" />
<SkewTransform CenterX="0.5" CenterY="0.5" />
<RotateTransform CenterX="0.5" CenterY="0.5" />
<TranslateTransform Y="0.47999998927116394" />
</TransformGroup>
</RadialGradientBrush.RelativeTransform>
<GradientStop Color="#FFFFC398" Offset="0.209" />
<GradientStop Color="#00FFB598" Offset="1" />
<GradientStop Color="#FFFFFFFF" Offset="0" />
</RadialGradientBrush>
<LinearGradientBrush x:Key="DisabledBackgroundBrushRed" EndPoint="0.5,1" StartPoint="0.5,0">
<GradientStop Color="#FFFFFFFF" />
<GradientStop Color="#FF62676A" Offset="1" />
<GradientStop Color="#FFD1D4D6" Offset="0.41800001263618469" />
<GradientStop Color="#FFA9AFB5" Offset="0.425" />
</LinearGradientBrush>
<Style x:Key="RedStyle" TargetType="Button">
<Setter Property="Background" Value="{StaticResource NormalBrushRed}"/>
<Setter Property="Foreground" Value="#FFFFFFFF"/>
<Setter Property="Padding" Value="3"/>
<Setter Property="BorderThickness" Value="2"/>
<Setter Property="BorderBrush" Value="{StaticResource NormalBorderBrushRed}" />
<Setter Property="Template">
<Setter.Value>
<!—Define Control Template Here-->
</Setter.Value>
</Setter>
</Style>
Listing
Custom Control definition in C# Code behind
We have seen so far controls designed in XAML and used, we have seen various states, animation and colors. But we can do a simple customization by adding a property to the existing control and then use it in XAML.
In the following sections we will see custom control definition in C# code behind.
Let's see the following example:
///<summary>
/// Custom control which is used for checking whether the treeview item is
root node or Last node of the tree
///</summary>
public class TreeViewItemEx:TreeViewItem
{
public bool IsRootNode { get; set; }
public bool IsEndNode { get; set; }
}
Listing
As you see in the example above, we have created a custom control by inheriting the Control. In the above example it is TreeViewItem. We have added two more properties to the Control such that the node's position can be checked for FIRST or LAST.
We can easily use the custom control by referencing the namespace and then using the control in the ControlTemplate.
Let's make a custom TextBox control that would restrict clipboard access from the user; commands such as CTRL+C, CTRL+X, and CTRL+V for Copy, Cut and Paste respectively.
First of all we need to create a class which would inherit TextBox.
public class CustomTextBox: TextBox
{
}
Listing
Add a Boolean property IsControlKeyDown, based on this property's value the associated key is taken as combination.
public bool IsControlKeyDown { get; set; }
Listing
Then we need to override the OnKeyUp and OnKeyDown events, such that we could write our own logic for that.
protected override void OnKeyDown(KeyEventArgs e)
{
if (e.Key== Key.Ctrl)
{
IsControlKeyDown = true;
}
if (IsControlKeyDown && (e.Key==Key.C || e.Key==Key.X || e.Key ==Key.V))
{
e.Handled = true;
}
else
{
base.OnKeyDown(e);
}
}
protected override void OnKeyUp(KeyEventArgs e)
{
if (e.Key == Key.Ctrl)
{
IsControlKeyDown = false;
}
base.OnKeyUp(e);
}
Listing
As you see in the above listing while overriding the OnKeyUp we are checking whether we have the CTRL key pressed or not, by checking the OnKeyDown we are checking which is the key combination. If they match such as CTRL + C, CTRL + X, and CTRL + V then we set e.Handled = true.
To use it in the XAML behind use the namespace, such that we could use the control.
Then we need to initialize the IsControlKeyDown to False, so that we can disable the Clipboard shortcuts in the custom Textbox.
Control Template and Binding
ControlTemplate can be bound to a property that is defined in an entity; such that the binding would happen dynamically.
Suppose, we need to display Employee Name in the ComboBox from a list of 4 properties defined in the entity.
public classEmployee
{
public int EmployeeID { get; set; }
public string EmployeeName { get; set; }
}
Listing
The above listing shows the Employee entity structure, where we have two different properties.
Let's have some sample data and then we would bind the sample data to the ComboBox'sItemsSource property.
public partial class MainPage : UserControl
{
public MainPage()
{
InitializeComponent();
List<Employee> employeeList = newList<Employee>
{
new Employee {EmployeeID=1, EmployeeName="Mahesh"},
new Employee {EmployeeID=1, EmployeeName="Mike"},
new Employee {EmployeeID=1, EmployeeName="Diptimaya"},
new Employee {EmployeeID=1, EmployeeName="Dhananjay"},
new Employee {EmployeeID=1, EmployeeName="Mamta"},
new Employee {EmployeeID=1, EmployeeName="Praveen"}
};
cmbEmployeeName.ItemsSource = employeeList;
}
}
Listing
As you see in the above listing we have the sample data and we have bound the data to the ComboBox. Now let's see the output.
Figure
As you see in above figure we have data displaying as the type of entity Employee. To display data correctly, we need to change the ItemTemplate style of the ComboBox.
<ComboBox x:Name="cmbEmployeeName"
VerticalAlignment="Top"
Margin="261,72,279,0"
Height="24" Width="200">
<ComboBox.ItemTemplate>
<DataTemplate>
<StackPanel>
<TextBlock Text="{Binding EmployeeName}"/>
</StackPanel>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
Listing
As you see from the listing above, we have customized the ItemTemplate of the ComboBox to display the proper result as per our entity. Now if you execute the application, you will see that the result is what we wanted. See the figure below.
Figure
UI Automation of a Silverlight Custom Control
Microsoft UI Automation provides a single, generalized interface that automation clients can use to examine or operate the user interfaces of a variety of platforms and frameworks. UI Automation enables both accessibility applications such as screen readers and quality-assurance (test) code to examine user-interface elements and simulate user interaction with them from other code.
Automation Peer Classes
Silverlight controls support UI Automation through a tree of peer classes that derive from AutomationPeer. By convention, peer class names begin with the control class name and end with "AutomationPeer". For example, ButtonAutomationPeer is the peer class for the Button control class. The peer classes are roughly equivalent to UI Automation control types but are specific to Silverlight elements. Automation code that accesses Silverlight applications through the UI Automation interface does not use automation peers directly, but automation code in the same process space can use automation peers directly. Automation peers must run in a partial-trust environment.
Built-in Automation Peer Classes
Elements implement an automation peer class if they accept interface activity from the user, or if they contain information needed by users of screen-reader applications. Not all Silverlight visual elements have automation peers. Examples of classes that implement automation peers are Button and TextBox. Examples of classes that do not implement automation peers are Border and classes based on Panel, such as Grid and Canvas.
The base Control class does not have a corresponding peer class. If you need a peer class to correspond to a custom control that derives from Control, you should derive the custom peer class from FrameworkElementAutomationPeer.
Peer Navigation
After locating an automation peer, in-process code can navigate the peer tree by calling the object's GetChildren and GetParent methods. Navigation among Silverlight elements within a control is supported by the peer's implementation of the GetChildrenCore method. The UI Automation system calls this method to build up a tree of subelements contained within a control; for example, list items in a list box. The default GetChildrenCore method in FrameworkElementAutomationPeer traverses the visual tree of elements to build the tree of automation peers. Custom controls override this method to expose children elements to automation clients, returning the automation peers of elements that convey information or allow user interaction.
Customizations in a Derived Peer
All classes that derive from UIElement contain the protected virtual method OnCreateAutomationPeer. Silverlight calls OnCreateAutomationPeer to get the automation peer object for each control. Automation code can use the peer to get information about a control's characteristics and features and to simulate interactive use. A custom control that supports automation must override OnCreateAutomationPeer and return an instance of a class that derives from AutomationPeer. For example, if a custom control derives from the ButtonBase class, then the object returned by OnCreateAutomationPeer should derive from ButtonBaseAutomationPeer.
When implementing a custom control, you must override the "Core" methods from the base automation peer class that describe behavior unique and specific to your custom control.
Override OnCreateAutomationPeer
Override the OnCreateAutomationPeer method for your custom control so that it returns your provider object, which must derive directly or indirectly from AutomationPeer.
Override GetPattern
Automation peers simplify some implementation aspects of server-side UI Automation providers, but custom control automation peers must still handle pattern interfaces. Like non-Silverlight providers, peers support control patterns by providing implementations of interfaces in the System.Windows.Automation.Provider namespace, such as IInvokeProvider.
The control pattern interfaces can be implemented by the peer itself or by another object. The peer's implementation of GetPattern returns the object that supports the specified pattern. UI Automation code calls the GetPattern method and specifies a PatternInterface enumeration value. Your override of GetPattern should return the object that implements the specified pattern.
If your control does not have a custom implementation of a pattern, you can call the base type's implementation of GetPattern to retrieve either its implementation or null if the pattern is not supported for this control type. For example, a custom numeric up-down control can be set to a value within a range, so its UI Automation peer would implement the IRangeValueProvider interface. The following example shows how the peer's GetPattern method is overridden to respond to a PatternInterface.RangeValue value.
A GetPattern method can also specify a subelement as a pattern provider. The following code shows how ItemsControl transfers scroll pattern handling to the peer of its internal ScrollViewer control.