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 ( ) から値を直接読み取った場合にも発生する可能性があります。Text
Header
Text
GetValue(TextProperty)
- 値の 1 つが失われるため、誰かが
Header
とText
プロパティの両方に値を提供すると、予期しない動作が発生する可能性があります。
全体として、このハックの使用はお勧めしません。プロジェクトを本当に管理している場合にのみ実行してください。コントロールが別のプロジェクトで使用される可能性が少しでもある場合は、StringFormat をあきらめてください。