2011-12-27 29 views
5

WPFアプリケーションでカスタムコントロールを使用しようとしていますが、StringFormatバインディングを使用していくつかの問題があります。カスタムコントロールの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ファイルを使用して、テーマフォルダを作成しました。のは、そこにコントロールのデフォルトのスタイルを入れてみましょう:

<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 ...テキストが画面に表示することです:

のHello World !

Test2:Hello World!

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

編集:回避策。

いいえ、TemplateBindingは誤解を招きました。私は原因と汚れた回避策を見つけました。

まず、問題はボタンのContentプロパティと同じであることに気づく:

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

だから、何が起こっているの? Reflectorを使用し、BindingBaseクラスのStringFormatプロパティに移動しましょう。 「分析」機能は、このプロパティが内部のDetermineEffectiveStringFormatメソッドによって使用されていることを示しています。この方法を見てみましょう:

ここに問題があります。 effectiveStringFormatフィールドは、Bindingを解決するときに使用されるフィールドです。また、このフィールドは、DependencyPropertyのタイプがStringである場合にのみ割り当てられます(これはButtonのコンテンツプロパティとしてObjectです)。

なぜオブジェクトですか?私のカスタムコントロールは、貼り付けたものよりも少し複雑なので、ボタンのように、コントロールのユーザーがテキストだけでなく子コントロールを提供できるようにしたい。

だから今は何ですか?私たちはWPFのコアコントロールでも動作しているので、そのままの状態にしておきます。

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のに使用されるプロパティです:それでも、私のカスタムコントロールとしてのみ内部のプロジェクトで使用され、私はXAMLから使いやすいようにしたいですが、私はこのハックを使用することにしました。値がTextに指定されている場合、プロパティのタイプがStringであるためStringFormatが適用され、コールバックを使用して値がHeaderプロパティに転送されます。それは動作しますが、それは本当に汚いです:私はHeaderを更新するときTextが更新されないよう

  • HeaderTextプロパティは、同期していません。何らかの間違いを避けるためにTextプロパティにgetterを提供しないことを選択しましたが、誰かがDependencyProperty(GetValue(TextProperty))から値を直接読み取ってもそれが起こる可能性があります。
  • 値のいずれかが失われるため、誰かがHeaderTextの両方のプロパティに値を提供すると、予期しない動作が発生することがあります。

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

答えて

2

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

+0

が間違っています。ランタイムは、文字列の書式を設定する前に、すべてのオブジェクトに対して 'object.ToString()'を呼び出します。 –

+2

はい、ただし、StringFormatを適用する前に、依存関係プロパティの基になる型をチェックします。私のdpの型をObjectではなくStringに変更すると動作します。 –

関連する問題