Windows Phone 7 Silverlight: Behaviors для TextBox
- modified:
- reading: 5 minutes
Термины Behaviors и Interactions ввели две библиотеки, поставляемые вместе с продуктом Expression Blend. Эти библиотеки еще известны со времен Silverlight и WPF, и предполагаю, что большинство разработчиков про них знает. Найти эти библиотеки можно в директории “c:\Program Files (x86)\Microsoft SDKs\Expression\Blend\” (если Windows 32 битный, то без (x86)), если Expression Blend был установлен. В этой папке вы сможете найти библиотеки для WPF/Silverlight/WindowsPhone. Зачем они нужны и как их правильно использовать вы можете узнать, пройдя по ссылки на MSDN Expression Blend SDK for Windows Phone. Если кратко: это способ расширят функциональность контролов, да еще и так, чтобы поддерживался паттерн MVVM (байндинги и т.п.).
При разработке своего первого приложения мне потребовалось несколько Behaviors для TextBox, которыми я и хочу с вами поделиться.
FocusOnLoadedBehavior
Если вы откроете редактирование контакта, а далее одного из пунктов контакта, на Windows Phone телефоне, то приложение установит сразу же фокус на первый TextBox. Достаточно удобно, так как, скорее всего, это то, что вам действительно нужно: вы открыли редактирование контакта, и, скорее всего, вы сразу же захотите поменять что-то. Спорный немного вопрос нужно ли устанавливать фокус, если у вас более одного TextBox на этой странице, в случае редактирования имени контакта он устанавливается, но все же, может быть, я собираюсь редактировать информацию только в третьем TextBox. Но в целом идея понятная. Я написал следующий Behavior:
/// <summary>
/// UI Behavior to set focus to <see cref="AssociatedObject"/> on loaded event.
/// </summary>
public class FocusOnLoadedBehavior : Behavior<Control>
{ public static readonly DependencyProperty SetFocusOnLoadedProperty =
DependencyProperty.Register("SetFocusOnLoaded", typeof(bool), typeof(FocusOnLoadedBehavior), new PropertyMetadata(true));
/// <summary>
/// Set focus to <see cref="AssociatedObject"/> on loaded event. By default <value>true</value>.
/// </summary>
public bool SetFocusOnLoaded
{ get { return (bool)GetValue(SetFocusOnLoadedProperty); }
set { SetValue(SetFocusOnLoadedProperty, value); }
}
protected override void OnAttached()
{ base.OnAttached();
AssociatedObject.Loaded += AssociatedObject_Loaded;
}
protected override void OnDetaching()
{ base.OnDetaching();
AssociatedObject.Loaded -= AssociatedObject_Loaded;
}
void AssociatedObject_Loaded(object sender, RoutedEventArgs e)
{
AssociatedObject.Loaded -= AssociatedObject_Loaded;
if (SetFocusOnLoaded)
{
AssociatedObject.Focus();
}
}
}
Все очень просто, добавляем этот Behavior для определенного TextBox на странице, и туда установится фокус при открытии страницы (на самом деле фокус устанавливается на Loaded событие контрола, а в случае добавления контролов в runtime – это будет именно Loaded контрола, а не страницы):
<TextBox MaxLength="255" Text="{Binding Path=DisplayName, Mode=TwoWay}"
xmlns:interactivity="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"
xmlns:oBehaviors="clr-namespace:outcold.Phone.Behaviors;assembly=outcold.Phone">
<interactivity:Interaction.Behaviors>
<oBehaviors:FocusOnLoadedBehavior />
</interactivity:Interaction.Behaviors>
</TextBox>
Само собой, пространства имен interactivity и oBehaviors вы можете вынести на более верхний элемент (на страницу, например).
У этого Behavior так же есть свойство SetFocusOnLoad, оно там для того, чтобы была возможность отключить установку фокуса на Load этого контрола. Сделано это для того, например, когда вам нужно в зависимости от ситуации поставить фокус в один из двух TextBox контролов.
TextBoxSelectOnFocusBehavior
В добавление к FocusOnLoadedBehavior часто нужно использовать и этот behavior. Он устанавливает курсор на заданную позицию. Давайте я сначала приведу его код:
public enum TextBoxFocusSelectType
{
None = 0,
SelectAll = 1,
SetCursorToTheEnd = 2
}
/// <summary>
/// UI Behavior to set how to select text in TextBox on Focused.
/// </summary>
public class TextBoxSelectOnFocusBehavior : Behavior<TextBox>
{ public TextBoxSelectOnFocusBehavior()
{
Type = TextBoxFocusSelectType.None;
}
/// <summary>
/// Set behavior of how to select text in <see cref="AssociatedObject"/>.
/// By default <value>TextBoxFocusSelectType.None</value>.
/// </summary>
public TextBoxFocusSelectType Type
{
get;
set;
}
protected override void OnAttached()
{ base.OnAttached();
AssociatedObject.GotFocus += AssociatedObject_GotFocus;
}
protected override void OnDetaching()
{ base.OnDetaching();
AssociatedObject.GotFocus -= AssociatedObject_GotFocus;
}
void AssociatedObject_GotFocus(object sender, RoutedEventArgs e)
{ if (Type == TextBoxFocusSelectType.SelectAll)
{
AssociatedObject.SelectAll();
} else if (Type == TextBoxFocusSelectType.SetCursorToTheEnd)
{
AssociatedObject.Select(AssociatedObject.Text.Length, 0);
}
}
}
Когда пользователь фокусируется на определенном TextBox, у него бывают разные потребности по изменению текста. Первая потребность – это полностью заменить текст, часто – при редактировании небольших цифр, дат. В этом случае нам нужно выделить весь текст в TextBox, чтобы при наборе цифр пользователь набрал все быстро с самого начала. Вторая потребность – это просто подредактировать текст, в случае Windows Phone, когда у нас нет кнопки Delete (предполагаю, что такая функция есть, но я ее не знаю), а только Backspace, то мы просто обязаны постоянно устанавливать курсор именно в конец текста.
Свойство Type я сделал без поддержки байндинга, у меня не было необходимости. Итого, пример может выглядеть так:
<TextBox MaxLength="255" Text="{Binding Path=DisplayName, Mode=TwoWay}"
xmlns:interactivity="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"
xmlns:oBehaviors="clr-namespace:outcold.Phone.Behaviors;assembly=outcold.Phone">
<interactivity:Interaction.Behaviors>
<oBehaviors:FocusOnLoadedBehavior />
<oBehaviors:TextBoxSelectOnFocusBehavior Type="SetCursorToTheEnd" />
</interactivity:Interaction.Behaviors>
</TextBox>
TextBoxInputRegexFilterBehavior
И последний на сегодня Behavior – это TextBoxInputRegexFilterBehavior, который помогает фильтровать текст, который пользователь хочет ввести. На самом деле, этот Behavior достаточно спорный, так как чрезмерное его употребление может смутить пользователя, и нужно его использовать только в очевидных местах. Один из таких моментов был у меня, когда мне было необходимо получить от пользователя целое число, а клавиатура с InputScope=”Number” содержит помимо цифр еще и точку. В одном из приложений в marketplace я видел, как какой-то разработчик написал собственную клавиатуру, чтобы решить эту проблему. Мне это решение совсем не понравилось, а так же я совсем не хочу использовать какие либо валидаторы в Windows Phone приложениях, экран слишком маленький для валидаторов. Поэтому я написал TextBoxInputRegexFilterBehavior:
/// <summary>
/// UI behavior for <see cref="TextBox"/> to filter input text with special RegularExpression
/// </summary>
public class TextBoxInputRegexFilterBehavior : Behavior<TextBox>
{ private Regex _regex;
private string _originalText;
private int _originalSelectionStart;
private int _originalSelectionLength;
public string RegularExpression
{ get { return _regex.ToString(); }
set
{ if (string.IsNullOrEmpty(value))
{ _regex = null;
} else
{ _regex = new Regex(value);
}
}
}
protected override void OnAttached()
{ base.OnAttached();
AssociatedObject.TextInputStart += AssociatedObject_TextInputStart;
AssociatedObject.TextChanged += AssociatedObject_TextChanged;
}
protected override void OnDetaching()
{ base.OnDetaching();
AssociatedObject.TextInputStart -= AssociatedObject_TextInputStart;
AssociatedObject.TextChanged -= AssociatedObject_TextChanged;
}
void AssociatedObject_TextInputStart(object sender, TextCompositionEventArgs e)
{ if (_regex != null && e.Text != null && !(e.Text.Length == 1 && Char.IsControl(e.Text[0])))
{ if (!_regex.IsMatch(e.Text))
{
_originalText = AssociatedObject.Text;
_originalSelectionStart = AssociatedObject.SelectionStart;
_originalSelectionLength = AssociatedObject.SelectionLength;
}
}
}
public void AssociatedObject_TextChanged(object sender, TextChangedEventArgs e)
{ if (_originalText != null)
{ string text = _originalText;
_originalText = null;
AssociatedObject.Text = text;
AssociatedObject.Select(_originalSelectionStart, _originalSelectionLength);
}
}
}
Использовать его можно, например, так:
<TextBox Text="{Binding Path=Value, Mode=TwoWay}" MaxLength="3" Width="100" InputScope="Number"
xmlns:interactivity="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"
xmlns:oBehaviors="clr-namespace:outcold.Phone.Behaviors;assembly=outcold.Phone">
<interactivity:Interaction.Behaviors>
<oBehaviors:TextBoxSelectOnFocusBehavior Type="SelectAll" />
<oBehaviors:TextBoxInputRegexFilterBehavior RegularExpression="[0-9]+" />
</interactivity:Interaction.Behaviors>
</TextBox>
В будущем я буду приводить и другие Behavior, которые мне помогают при разработке. Вы же обязательно ознакомьтесь с документацией на MSDN, так как эти библиотеки несут в себе еще и замечательные триггеры, а так же готовые Behaviors.