我正在尝试在WPF应用程序中使用自定义控件,并且使用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文件的Themes文件夹。让我们把控件的默认样式放在那里:
<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>
现在,只需将控件添加到窗口,然后使用StringFormat在Text属性上设置绑定。还要添加一个简单的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>
编译,运行,aaaa ...窗口上显示的文字是:
Hello World!
测试2:Hello World!
在自定义控件上,完全忽略StringFormat。 VS输出窗口上没有可见的错误。发生了什么事?
修改:解决方法。
好的,TemplateBinding具有误导性。我找到了原因和一个肮脏的解决方法。
首先,请注意问题与Button的Content属性相同:
<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的内容属性,Object
)。
为何选择对象?因为我的自定义控件比我粘贴的那个复杂一点,并且像Button一样,我希望控件的用户能够提供子控件而不仅仅是文本。
那么,现在呢?即使在WPF核心控件中,我们也遇到了一种行为,因此我可以“按原样”保留它。尽管如此,由于我的自定义控件仅用于内部项目,并且我希望它更容易在XAML中使用,我决定使用此hack:
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
属性提供getter以避免一些错误,但如果有人直接从DependencyProperty(GetValue(TextProperty)
)读取值,它仍然会发生。Header
和Text
属性提供了值,则可能会发生不可预测的行为,因为其中一个值将丢失。总的来说,我不建议使用这个hack。只有当你真的控制你的项目时才这样做。如果控件在其他项目中使用的机会最小,那就放弃StringFormat。
答案 0 :(得分:6)
使用TemplateBinding时,您无法传递StringFormat或Converter。这里有一些解决方法。
答案 1 :(得分:2)
StringFormat
属性时使用 string
,而控件中的Text
属性类型为object
,因此忽略StringFormat
。