2011-12-27 105 views
5

我想在WPF应用程序中使用自定义控件,并且使用StringFormat绑定时出现了一些问题。在自定义控件上绑定StringFormat

问题很容易重现。首先,我们创建一个WPF应用程序并将其称为“TemplateBindingTest”。在那里,添加一个只有一个属性(Text)的自定义ViewModel,并将其分配给Window的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是误导。我发现原因和肮脏的解决方法。

首先,请注意这个问题是按钮的内容属性是相同的:

<Button Content="{Binding Path=Text, StringFormat=Test3: {0}}" /> 

那么,这是怎么回事?让我们使用Reflector并潜入BindingBase类的StringFormat属性。 “分析”功能显示该属性由内部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(我的Button为Content的内容属性Object)时才会分配此字段。

为什么选择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属性。它的工作原理,但它确实脏:

  • HeaderText财产不同步,如不更新Text当我更新Header。我选择不为Text属性提供吸气剂以避免一些错误,但如果有人直接从DependencyProperty(GetValue(TextProperty))中读取值,它仍然可能发生。
  • 如果某人向HeaderText属性提供值,则会发生不可预知的行为,因为其中一个值将丢失。

总的来说,我不会推荐使用这个黑客。只有在您的项目是真的才能控制您的项目。如果控件在其他项目中使用的机会甚微,那就放弃StringFormat。

回答

2

StringFormat结合到string属性时被使用,而在控制的Text属性是object型的,因此StringFormat被忽略。

+0

错误。在格式化字符串之前,运行时为所有对象调用'object.ToString()'。 – 2011-12-27 14:05:25

+2

是的,但它在应用StringFormat之前检查依赖项属性的基础类型。如果我将dp的类型更改为String而不是Object,它会有效。 – 2011-12-27 14:11:06