Связывание с StringFormat в пользовательском элементе управления - PullRequest
5 голосов
/ 27 декабря 2011

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

Проблема легко воспроизводится. Сначала давайте создадим приложение WPF и назовем его «TemplateBindingTest». Там добавьте пользовательскую ViewModel только с одним свойством (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 автоматически создает папку «Темы» с файлом 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>

Компилировать, запускать, aaaaand ... Текст, отображаемый в окне:

Hello World!

Test2: Hello World!

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

Редактировать: Обходной путь.

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

Во-первых, обратите внимание, что проблема аналогична свойству Content для Button:

<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
    }
}

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

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

Так что теперь? Мы сталкиваемся с поведением, существующим даже в элементах управления ядра 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 с помощью обратного вызова. Это работает, но это действительно грязно:

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

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

Ответы [ 2 ]

6 голосов
/ 27 декабря 2011

Вы не можете передать StringFormat или Converter при использовании TemplateBinding. Вот несколько обходных путей.

2 голосов
/ 27 декабря 2011

StringFormat используется при привязке к свойству string, а свойство Text в вашем элементе управления имеет тип object, поэтому StringFormat игнорируется.

...