2011-12-27 2 views
5

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

ответ

2

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

+0

Неверный. Среда выполнения вызывает 'object.ToString()' для всех объектов перед форматированием строки. –

+2

Да, но он проверяет базовый тип свойства зависимостей перед применением StringFormat. Он работает, если я изменяю тип моего dp на String вместо Object. –