【问题标题】:Binding with StringFormat on a custom control在自定义控件上使用 StringFormat 绑定
【发布时间】:2012-01-28 12:26:52
【问题描述】:

我正在尝试在 WPF 应用程序中使用自定义控件,但在使用 StringFormat 绑定时遇到了一些问题。

这个问题很容易重现。首先,让我们创建一个 WPF 应用程序并将其命名为“TemplateBindingTest”。在那里,添加一个只有一个属性(文本)的自定义 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 自动创建了一个 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>

现在,只需将控件添加到窗口,并使用 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>

编译,运行,aaaaand... 窗口显示的文字是:

世界你好!

Test2:世界你好!

在自定义控件上,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 字段是解析绑定时使用的字段。并且仅当 DependencyProperty 的类型为 String 时才分配此字段(我的是,作为 Button 的 Content 属性,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 属性不同步,因为当我更新Header 时Text 没有更新。我选择不为 Text 属性提供 getter 以避免一些错误,但如果有人直接从 DependencyProperty (GetValue(TextProperty)) 读取值,仍然会发生这种情况。
  • 如果有人同时为 Header 和 Text 属性提供值,则可能会发生不可预测的行为,因为其中一个值将丢失。

所以总的来说,我不建议使用这个 hack。仅当您真正控制您的项目时才这样做。如果控件有一丝一毫被用在另一个项目上的机会,那就放弃StringFormat吧。

【问题讨论】:

    标签: c# wpf string-formatting templatebinding


    【解决方案1】:

    使用 TemplateBinding 时不能传递 StringFormat 或 Converter。以下是一些解决方法。

    【讨论】:

      【解决方案2】:

      StringFormat 在绑定到string 属性时使用,而您控件中的Text 属性的类型为object,因此StringFormat 被忽略。

      【讨论】:

      • 不正确。运行时在格式化字符串之前为所有对象调用object.ToString()。
      • 是的,但它会在应用 StringFormat 之前检查依赖属性的基础类型。如果我将 dp 的类型更改为 String 而不是 Object,它会起作用。
      猜你喜欢
      • 1970-01-01
      • 2016-04-15
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2017-02-16
      相关资源
      最近更新 更多