Я пытаюсь использовать настраиваемый элемент управления в приложении WPF, и у меня есть проблема с привязкой StringFormat.Связывание с StringFormat на настраиваемом элементе управления
Проблема легко воспроизвести. Сначала давайте создадим приложение WPF и назовем его «TemplateBindingTest». Там добавьте пользовательский ViewModel только с одним свойством (Текст) и назначьте его в 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 нет ошибки. Что происходит?
Редактировать: Обходной путь.
Ok, TemplateBinding вводит в заблуждение. Я нашел причину и грязное обходное решение.
Во-первых, обратите внимание, что эта проблема является то же самое с контентом свойство 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
}
}
Проблема здесь. Поле effectiveStringFormat - это поле, используемое при разрешении привязки. И это поле назначается только в том случае, если DependencyProperty имеет тип String
(мой, как свойство Content Button, 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
иText
свойство не синхронизированы, посколькуText
не обновляется при обновленииHeader
. Я предпочитаю не использовать getter для свойстваText
, чтобы избежать некоторых ошибок, но это может произойти, если кто-то прямо читает значение из DependencyProperty (GetValue(TextProperty)
).- Непредсказуемое поведение может случиться, если кто-то предоставит значение как
Header
, так иText
, поскольку одно из значений будет потеряно.
В общем, я бы не рекомендовал использовать этот хак. Делайте это только в том случае, если вы действительно в управлении вашим проектом. Если у элемента управления есть хоть малейшая вероятность быть использованным в другом проекте, просто оставьте StringFormat.
Неверный. Среда выполнения вызывает 'object.ToString()' для всех объектов перед форматированием строки. –
Да, но он проверяет базовый тип свойства зависимостей перед применением StringFormat. Он работает, если я изменяю тип моего dp на String вместо Object. –