5

WPF アプリでカスタム コントロールを使用しようとしていますが、StringFormat バインディングの使用に問題があります。

問題は簡単に再現できます。まず、WPF アプリケーションを作成し、「TemplateBindingTest」と呼びましょう。そこで、1 つのプロパティ (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>

コンパイル、実行、aaaaand... ウィンドウに表示されるテキストは次のとおりです。

「こんにちは世界」

Test2: ハローワールド!

カスタム コントロールでは、StringFormat は完全に無視されます。VS 出力ウィンドウにエラーは表示されません。どうしたの?

編集:回避策。

OK、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(私の場合は、ボタンの Content プロパティとして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ます。動作しますが、本当に汚れています:

  • Headerとプロパティは、更新時に更新されTextないため、同期されていません。いくつかの間違いを避けるためにプロパティに getter を提供しないことにしましたが、誰かが DependencyProperty ( ) から値を直接読み取った場合にも発生する可能性があります。TextHeaderTextGetValue(TextProperty)
  • 値の 1 つが失われるため、誰かがHeaderTextプロパティの両方に値を提供すると、予期しない動作が発生する可能性があります。

全体として、このハックの使用はお勧めしません。プロジェクトを本当に管理している場合にのみ実行してください。コントロールが別のプロジェクトで使用される可能性が少しでもある場合は、StringFormat をあきらめてください。

4

2 に答える 2

6

TemplateBinding を使用する場合、StringFormat または Converter を渡すことはできません。ここにいくつかの回避策があります。

于 2011-12-27T13:05:35.693 に答える
3

StringFormatstringプロパティにバインドするときに使用さTextれますが、コントロール内のプロパティのタイプobjectは であるため、StringFormat無視されます。

于 2011-12-27T13:59:16.073 に答える