When you use custom controls in your silverlight application, you should implement custom AutomationPeer classes. These AutomationPeer classes will be used in your unittests. Below some different flavours I use in my project.
- Inherited, when you inherit from a control that already has an AutomationPeer you can redirect everything to that implementation.
/// <summary>
/// Special button that inherits from the Button control
/// </summary>
public class SpecialButton : System.Windows.Controls.Button
{
// your magic happens here
}
/// <summary>
/// Exposes SpecialButton types to UI automation.
/// </summary>
public class SpecialButtonAutomationPeer : System.Windows.Automation.Peers.ButtonAutomationPeer
{
/// <summary>
/// Initializes a new instance of the <see cref="SpecialButtonAutomationPeer"/> class.
/// </summary>
/// <param name="owner">The owner.</param>
public SpecialButtonAutomationPeer(SpecialButton owner) : base(owner)
{
}
}
Wrapped, when you wrap a control that already has an AutomationPeer you can redirect everything to that implementation.
/// <summary>
/// Special textbox that wraps a TextBox control
/// </summary>
public class SpecialTextBox
{
// internal to expose within the assembly
// protected to expose to controls who inherit this SpecialTextBox
internal protected System.Windows.Controls.TextBox _textBox;
// your magic happens here
}
/// <summary>
/// Exposes SpecialTextBox types to UI automation.
/// </summary>
public class SpecialTextBoxAutomationPeer : System.Windows.Automation.Peers.TextBoxAutomationPeer
{
/// <summary>
/// Initializes a new instance of the <see cref="SpecialTextBoxAutomationPeer"/> class.
/// </summary>
/// <param name="owner">The owner.</param>
public SpecialButtonAutomationPeer(SpecialTextBox owner) : base(owner._textBox)
{
}
}
Composite 1, when you use multiple controls that already have an AutomationPeer you can redirect everything to that implementation, but you need to direct this. You expose an AutomationPeer for every control on the composite control
/// <summary>
/// Special time control that is a composite of two ComboBox controls
/// </summary>
public class SpecialTimeControl : System.Windows.Controls.Control
{
// internal to expose within the assembly
// protected to expose to controls who inherit this SpecialTimeControl
internal protected ComboBox _hoursComboBox;
internal protected ComboBox _minutesComboBox;
// your magic happens here
}
/// <summary>
/// Exposes ComboBoxAutomationPeer types to UI automation.
/// </summary>
public class SpecialTimeControlAutomationPeer : System.Windows.Automation.Peers.ComboBoxAutomationPeer
{
/// <summary>
/// Enumeration for selecting the timepart control
/// </summary>
public enum TimePartControl
{
/// <summary>
/// Control for providing the Hours value
/// </summary>
Hours = 0,
/// <summary>
/// Control for providing the Minutes value
/// </summary>
Minutes = 1
}
/// <summary>
/// Initializes a new instance of the <see cref="SpecialTimeControlAutomationPeer"/> class.
/// </summary>
/// <param name="owner">The owner.</param>
/// <param name="control">The part of the control to expose.</param>
public SpecialTimeControlAutomationPeer(SpecialTimeControl owner, TimePartControl control) : base(SelectControl(owner, control))
{
}
/// <summary>
/// Selects the control.
/// </summary>
/// <param name="owner">The owner.</param>
/// <param name="selection">The selection.</param>
/// <returns></returns>
private static ComboBox SelectControl(SpecialTimeControl owner, TimePartControl selection)
{
ComboBox result = null;
switch (selection)
{
case TimePartControl.Hours:
result = owner._hoursComboBox;
break;
case TimePartControl.Minutes:
result = owner._minutesComboBox;
break;
}
return result;
}
}
// example: set the hour part of the time control to 10
var hoursComboBoxPeer = new SpecialTimeControlAutomationPeer(ctrl, SpecialTimeControlAutomationPeer.TimePartControl.Hours);
var hour = "10";
var hoursComboBoxSelectionItem = (ISelectionItemProvider)hoursComboBoxPeer.GetChildren()
.Where(x => x.GetName().Equals(hour))
.First()
.GetPattern(PatternInterface.SelectionItem);
hoursComboBoxSelectionItem.Select();
Composite 2, when you use multiple controls that already have an AutomationPeer you can redirect everything to that implementation, but you need to direct this. You implement the provider interface on the custom AutomationPeerclass.
/// <summary>
/// Exposes SpecialTimeControl types to UI automation.
/// </summary>
public class SpecialTimeControlAutomationPeer : System.Windows.Automation.Peers.SelectorAutomationPeer,
System.Windows.Automation.Provider.IValueProvider
{
protected System.Windows.Automation.Peers.ComboBoxAutomationPeer _hoursComboBoxAutomationPeer;
protected System.Windows.Automation.Provider.IValueProvider _hoursValueProvider;
protected System.Windows.Automation.Peers.ComboBoxAutomationPeer _minutesComboBoxAutomationPeer;
protected System.Windows.Automation.Provider.IValueProvider _minutesValueProvider;
/// <summary>
/// Initializes a new instance of the <see cref="SpecialTimeControlAutomationPeer"/> class.
/// </summary>
/// <param name="owner">The owner.</param>
public SpecialTimeControlAutomationPeer(SpecialTimeControl owner) : base(owner._hoursComboBox)
{
// construct the AutomationPeer and IValueProvider objects that will do all the actual work
_hoursComboBoxAutomationPeer = new System.Windows.Automation.Peers.ComboBoxAutomationPeer(owner._hoursComboBox);
_hoursValueProvider = (System.Windows.Automation.Provider.IValueProvider)_hoursComboBoxAutomationPeer.GetPattern(System.Windows.Automation.Peers.PatternInterface.Value);
_minutesComboBoxAutomationPeer = new System.Windows.Automation.Peers.ComboBoxAutomationPeer(owner._minutesComboBox);
_minutesValueProvider = (System.Windows.Automation.Provider.IValueProvider) _minutesComboBoxAutomationPeer.GetPattern(System.Windows.Automation.Peers.PatternInterface.Value);
}
/// <summary>
/// Gets the control pattern for the <see cref="T:System.Windows.Controls.Primitives.Selector"/> that is associated with this <see cref="T:System.Windows.Automation.Peers.SelectorAutomationPeer"/>.
/// </summary>
/// <param name="patternInterface">One of the enumeration values.</param>
/// <returns>
/// The object that implements the pattern interface, or null if the specified pattern interface is not implemented by this peer.
/// </returns>
public override object GetPattern(System.Windows.Automation.Peers.PatternInterface patternInterface)
{
// this class implements the IValueProvider so return it
if (patternInterface == System.Windows.Automation.Peers.PatternInterface.Value) return this;
else return base.GetPattern(patternInterface);
}
/// <summary>
/// Gets a value that indicates whether the value of a control is read-only.
/// </summary>
/// <returns>true if the value is read-only; false if it can be modified. </returns>
public bool IsReadOnly
{
get
{
return _hoursValueProvider.IsReadOnly || _hoursValueProvider.IsReadOnly;
}
}
/// <summary>
/// Sets the value of a control.
/// </summary>
/// <param name="value">The value to set. The provider is responsible for converting the value to the appropriate data type.</param>
public void SetValue(string value)
{
// value should be a datetime
var date = DateTime.Parse(value);
// get the parts
var hour = date.Hour.ToString();
var minute = date.Minute.ToString("00");
// find the item in the children that matches
// the value for hour in the date
var hoursComboBoxSelectionItem =
(System.Windows.Automation.Provider.ISelectionItemProvider)
_hoursComboBoxAutomationPeer.GetChildren()
.Where(x => x.GetName().Equals(hour))
.First() // throws an expection if not found
.GetPattern(System.Windows.Automation.Peers.PatternInterface.SelectionItem);
// select the hour
hoursComboBoxSelectionItem.Select();
// find the item in the children that matches
// the value for minute in the date
var minutesComboBoxSelectionItem =
(System.Windows.Automation.Provider.ISelectionItemProvider)
_minutesComboBoxAutomationPeer.GetChildren()
.Where(x => x.GetName().Equals(minute))
.First() // throws an expection if not found
.GetPattern(System.Windows.Automation.Peers.PatternInterface.SelectionItem);
// select the minute
minutesComboBoxSelectionItem.Select();
}
/// <summary>
/// Gets the value of the control.
/// </summary>
/// <returns>The value of the control.</returns>
public string Value
{
get
{
var hour = int.Parse(_hoursValueProvider.Value);
var minute = int.Parse(_minutesValueProvider.Value);
// use defaults for the year, month, day and seconds
var date = new DateTime(2000, 1, 1, hour, minute, 0);
return date.ToString();
}
}
}
// example: set the complete time to the current time
var specialTimeAutomationPeer = new SpecialTimeControlAutomationPeer(ctrl);
var specialTimeValueProvider = specialTimeAutomationPeer.GetPattern(System.Windows.Automation.Peers.PatternInterface.Value);
specialTimeValueProvider.SetValue(DateTime.Now); // uses the current time
The choice between the two composite controls is up to you. Maybe you need both. The difference is the place where the controls are set. In composite 1 this is done in your testcode which gives you the possibility to do some wrong inputs. In composite 2 the code is kept out of the testcode which provides more robust tests.