Привязка со StringFormat к настраиваемому элементу управления

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

Проблему легко воспроизвести Во-первых, давайте создадим приложение WPF и назовем его TemplateBindingTest. Там добавим настраиваемую модель представления только с одним свойством (Text) и назначим его DataContext окна. Установите для свойства Text значение «Hello World!».

Теперь добавьте в решение пользовательский элемент управления. Пользовательский элемент управления настолько прост, насколько это возможно:

using System.Windows;
using System.Windows.Controls;

namespace TemplateBindingTest
{
    public class CustomControl : Control
    {
        static CustomControl()
        {
            TextProperty = DependencyProperty.Register(
                "Text",
                typeof(object),
                typeof(CustomControl),
                new FrameworkPropertyMetadata(null));

            DefaultStyleKeyProperty.OverrideMetadata(typeof(CustomControl), new FrameworkPropertyMetadata(typeof(CustomControl)));
        }

        public static DependencyProperty TextProperty;

        public object Text
        {
            get
            {
                return this.GetValue(TextProperty);
            }

            set
            {
                SetValue(TextProperty, value);
            }
        }
    }
}

При добавлении пользовательского элемента управления в решение Visual Studio автоматически создала папку Themes с файлом generic.xaml Давайте поместим туда стиль по умолчанию для элемента управления:

<ResourceDictionary
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="clr-namespace:TemplateBindingTest">

    <Style TargetType="{x:Type local:CustomControl}">
        <Setter Property="Template">
            <Setter.Value>
                <ControlTemplate TargetType="{x:Type local:CustomControl}">
                    <TextBlock Text="{TemplateBinding Text}" />
                </ControlTemplate>
            </Setter.Value>
        </Setter>
    </Style>
</ResourceDictionary>

Теперь просто добавьте элемент управления в окно и установите привязку к свойству Text, используя StringFormat.Также добавьте простой TextBlock, чтобы убедиться, что синтаксис привязки правильный:

<Window x:Class="TemplateBindingTest.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:TemplateBindingTest="clr-namespace:TemplateBindingTest" Title="MainWindow" Height="350" Width="525">
<StackPanel>
    <TemplateBindingTest:CustomControl Text="{Binding Path=Text, StringFormat=Test1: {0}}"/>
    <TextBlock Text="{Binding Path=Text, StringFormat=Test2: {0}}" />
</StackPanel>

Compile, run, aaaaand ... Текст, отображаемый в окне:

Hello World!

Test2: Hello World!

В настраиваемом элементе управления StringFormat полностью игнорируется. В окне вывода VS ошибки не видно. Что происходит?

Изменить: Временное решение.

Хорошо, TemplateBinding вводил в заблуждение. Я нашел причину и грязное решение.

Во-первых, обратите внимание, что проблема та же самая со свойством Content кнопки:

<Button Content="{Binding Path=Text, StringFormat=Test3: {0}}" />

Итак, что происходит? Давайте воспользуемся Reflector и перейдем к свойству StringFormat класса BindingBase. Функция «Анализ» показывает, что это свойство используется внутренним методом DetermineEffectiveStringFormat . Давайте посмотрим на этот метод:

internal void DetermineEffectiveStringFormat()
{
    Type propertyType = this.TargetProperty.PropertyType;
    if (propertyType == typeof(string))
    {
         // Do some checks then assign the _effectiveStringFormat field
    }
}

Проблема прямо здесь. Поле EffectiveStringFormat используется при разрешении Binding. И это поле назначается только в том случае, если DependencyProperty имеет тип String (мое, как свойство Content кнопки, Object ).

Почему возражать? Поскольку мой настраиваемый элемент управления немного сложнее, чем тот, который я вставил, и, как и Button, я хочу, чтобы пользователь элемента управления мог предоставлять дочерние элементы управления, а не просто текст.

Итак, что теперь? Мы сталкиваемся с поведением, существующим даже в основных элементах управления WPF, поэтому я могу просто оставить его «как есть». Тем не менее, поскольку мой настраиваемый элемент управления используется только во внутреннем проекте, и я хочу, чтобы его было проще использовать из XAML, я решил использовать этот прием:

using System.Windows;
using System.Windows.Controls;

namespace TemplateBindingTest
{
    public class CustomControl : Control
    {
        static CustomControl()
        {
            TextProperty = DependencyProperty.Register(
                "Text",
                typeof(string),
                typeof(CustomControl),
                new FrameworkPropertyMetadata(null, Callback));

            HeaderProperty = DependencyProperty.Register(
                "Header",
                typeof(object),
                typeof(CustomControl),
                new FrameworkPropertyMetadata(null));

            DefaultStyleKeyProperty.OverrideMetadata(typeof(CustomControl), new FrameworkPropertyMetadata(typeof(CustomControl)));
        }

        static void Callback(DependencyObject obj, DependencyPropertyChangedEventArgs e)
        {
            obj.SetValue(HeaderProperty, e.NewValue);
        }

        public static DependencyProperty TextProperty;
        public static DependencyProperty HeaderProperty;

        public object Header
        {
            get
            {
                return this.GetValue(HeaderProperty);
            }

            set
            {
                SetValue(HeaderProperty, value);
            }
        }

        public string Text
        {
            set
            {
                SetValue(TextProperty, value);
            }
        }
    }
}

Header - это свойство, используемое в моем TemplateBinding.Когда значение предоставляется для Text , применяется StringFormat, поскольку свойство имеет тип String , затем значение перенаправляется в свойство Header с помощью обратного вызова . Это работает, но действительно грязно:

  • Заголовок и свойство Text не синхронизируются, поскольку Текст не обновляется при обновлении Заголовок . Я решил не предоставлять геттер для свойства Text , чтобы избежать некоторых ошибок, но это все равно может произойти, если кто-то напрямую прочитает значение из DependencyProperty ( GetValue (TextProperty) ).
  • Непредсказуемое поведение может произойти, если кто-то предоставит значение для свойств Заголовок и Текст , поскольку одно из значений будет потеряно.

В общем, я бы не рекомендовал использовать этот прием. Делайте это, только если вы действительно контролируете свой проект. Если у элемента управления есть хотя бы малейшая вероятность использования в другом проекте, просто откажитесь от StringFormat.

5
задан Tomislav Markovski 27 December 2011 в 14:07
поделиться