Изменение параметров привязки текста TextBox с помощью стиля

Я хотел бы иметь TextBox, отображающий число в формате валюты (путем установки StringFormat=c в привязке). Когда выбран TextBox (когда IsKeyboardFocused==true), я бы хотел, чтобы форматирование исчезло, пока фокус на TextBox не будет потерян.

Я нашел способ сделать это, код вставлен ниже. Моя проблема заключается в том, что привязка указана внутри Style - это означает, что я должен перепечатать стиль для каждого TextBox, для которого я хочу это сделать. В идеале я хотел бы поместить стиль где-нибудь в центре и повторно использовать его для каждого TextBox с разными целями привязки для каждого.

Есть ли способ, используя Style, установить параметр существующей привязки, что-то вроде Text.Binding.StringFormat=""? (В отличие от установки всего значения Text на вновь определенную привязку)

Другие предложения для достижения этого также будут оценены.

Код (это работает, просто неудобно):

<TextBox x:Name="ContractAmountTextBox">
<TextBox.Style>
    <Style TargetType="{x:Type TextBox}">                                       
        <Style.Triggers>
            <DataTrigger Binding="{Binding IsKeyboardFocused, ElementName=ContractAmountTextBox}" Value="False">
                <Setter Property="Text" Value="{Binding Path=ContractAmount, UpdateSourceTrigger=LostFocus, StringFormat=c}"/>
            </DataTrigger>
            <DataTrigger Binding="{Binding IsKeyboardFocused, ElementName=ContractAmountTextBox}" Value="True">
                <Setter Property="Text" Value="{Binding Path=ContractAmount, UpdateSourceTrigger=LostFocus}"/>
            </DataTrigger>
        </Style.Triggers>
    </Style>
</TextBox.Style>


person Pieter Müller    schedule 08.04.2011    source источник


Ответы (1)


Это возможно с прикрепленным свойством, но это означает, что вам придется полностью заменить привязку, а затем вернуть ее обратно.

Вот быстрая и грязная реализация:

public static class TextBoxBehavior
{

    #region StringFormat

    public static string GetStringFormat(TextBox obj)
    {
        return (string)obj.GetValue(StringFormatProperty);
    }

    public static void SetStringFormat(TextBox obj, string value)
    {
        obj.SetValue(StringFormatProperty, value);
    }


    public static readonly DependencyProperty StringFormatProperty =
        DependencyProperty.RegisterAttached(
          "StringFormat",
          typeof(string),
          typeof(TextBoxBehavior),
          new UIPropertyMetadata(
            null,
            StringFormatChanged));

    // Used to store the original format
    private static readonly DependencyPropertyKey OriginalBindingPropertyKey =
        DependencyProperty.RegisterAttachedReadOnly(
            "OriginalBinding",
            typeof(BindingBase),
            typeof(TextBoxBehavior),
            new UIPropertyMetadata(null));

    private static void StringFormatChanged(DependencyObject o, DependencyPropertyChangedEventArgs e)
    {
        TextBox textBox = o as TextBox;
        if (textBox == null)
            return;

        string oldValue = (string)e.OldValue;
        string newValue = (string)e.NewValue;

        if (!string.IsNullOrEmpty(oldValue) && string.IsNullOrEmpty(newValue))
        {
            // Update target for current binding
            UpdateTextBindingSource(textBox);

            // Restore original binding
            var originalBinding = (BindingBase)textBox.GetValue(OriginalBindingPropertyKey.DependencyProperty);
            if (originalBinding != null)
                BindingOperations.SetBinding(textBox, TextBox.TextProperty, originalBinding);
            textBox.SetValue(OriginalBindingPropertyKey, null);
        }
        else if (!string.IsNullOrEmpty(newValue) && string.IsNullOrEmpty(oldValue))
        {
            // Get current binding
            var originalBinding = BindingOperations.GetBinding(textBox, TextBox.TextProperty);
            if (originalBinding != null)
            {
                // Update target for current binding
                UpdateTextBindingSource(textBox);

                // Create new binding
                var newBinding = CloneBinding(originalBinding);
                newBinding.StringFormat = newValue;

                // Assign new binding
                BindingOperations.SetBinding(textBox, TextBox.TextProperty, newBinding);

                // Store original binding
                textBox.SetValue(OriginalBindingPropertyKey, originalBinding);
            }
        }
    }

    private static void UpdateTextBindingSource(TextBox textBox)
    {
        var expr = textBox.GetBindingExpression(TextBox.TextProperty);
        if (expr != null &&
            expr.ParentBinding != null &&
            (expr.ParentBinding.Mode == BindingMode.Default // Text binds two-way by default
            || expr.ParentBinding.Mode == BindingMode.TwoWay
            || expr.ParentBinding.Mode == BindingMode.OneWayToSource))
        {
            expr.UpdateSource();
        }
    }

    private static Binding CloneBinding(Binding original)
    {
        var copy = new Binding
                         {
                             Path = original.Path,
                             XPath = original.XPath,
                             Mode = original.Mode,
                             Converter = original.Converter,
                             ConverterCulture = original.ConverterCulture,
                             ConverterParameter = original.ConverterParameter,
                             FallbackValue = original.FallbackValue,
                             TargetNullValue = original.TargetNullValue,
                             NotifyOnSourceUpdated = original.NotifyOnSourceUpdated,
                             NotifyOnTargetUpdated = original.NotifyOnTargetUpdated,
                             NotifyOnValidationError = original.NotifyOnValidationError,
                             UpdateSourceExceptionFilter = original.UpdateSourceExceptionFilter,
                             UpdateSourceTrigger = original.UpdateSourceTrigger,
                             ValidatesOnDataErrors = original.ValidatesOnDataErrors,
                             ValidatesOnExceptions = original.ValidatesOnExceptions,
                             BindingGroupName = original.BindingGroupName,
                             BindsDirectlyToSource = original.BindsDirectlyToSource,
                             AsyncState = original.AsyncState,
                             IsAsync = original.IsAsync,
                             StringFormat = original.StringFormat
                         };

        if (original.Source != null)
            copy.Source = original.Source;
        if (original.RelativeSource != null)
            copy.RelativeSource = original.RelativeSource;
        if (original.ElementName != null)
            copy.ElementName = original.ElementName;

        foreach (var rule in original.ValidationRules)
        {
            copy.ValidationRules.Add(rule);
        }
        return copy;
    }

    #endregion
}

Использование:

<TextBox x:Name="ContractAmountTextBox"
         Text="{Binding Path=ContractAmount, UpdateSourceTrigger=LostFocus, StringFormat=c}">
    <TextBox.Style>
        <Style TargetType="{x:Type TextBox}">                                       
            <Style.Triggers>
                <Trigger Property="IsKeyboardFocused" Value="True">
                    <Setter Property="local:TextBoxBehavior.StringFormat" Value="N"/>
                </Trigger>
            </Style.Triggers>
        </Style>
    </TextBox.Style>
</TextBox>

Используя это, вы также можете повторно использовать стиль для разных текстовых полей.

person Thomas Levesque    schedule 08.04.2011
comment
Спасибо, Томас, довольно аккуратно. У меня есть два вопроса. В привязке DataTrigger вашего стиля вы ссылаетесь на ElementName. Как это можно обобщить, если я хочу определить стиль в словаре ресурсов и просто повторно использовать его для нескольких текстовых полей? Во-вторых, я обеспокоен непредвиденными побочными эффектами, вызванными заменой привязки вручную. Может ли случиться так, что модель Binding будет расширена в будущей версии .NET, и мы больше не будем копировать все параметры? - person Pieter Müller; 19.04.2011
comment
Что касается ElementName, на самом деле я просто скопировал ваш код;). Нет необходимости в ElementName, и вы можете использовать обычный триггер вместо DataTrigger. Я обновлю свой ответ. - person Thomas Levesque; 19.04.2011