Я пытаюсь использовать пользовательский элемент управления в приложении 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.