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