Windows Phone 7 Silverlight: Behaviors для TextBox

    • Silverlight
    • XAML
    • Expression Blend
    • Binding
    • Windows Phone 7
    • Behavior
    • Blend
    • Interactivity
  • 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.

See Also