9

添付プロパティを使用して TextBox の背景にプロンプ​​ト テキスト ラベルを作成しようとしていますが、スタイル リソースのテキスト キャプションへのバインディングを解決できません。

スタイル定義:

<Style x:Key="CueBannerTextBoxStyle"
       TargetType="TextBox">
  <Style.Resources>
    <VisualBrush x:Key="CueBannerBrush"
                 AlignmentX="Left"
                 AlignmentY="Center"
                 Stretch="None">
      <VisualBrush.Visual>
        <Label Content="{Binding Path=(EnhancedControls:CueBannerTextBox.Caption), RelativeSource={RelativeSource AncestorType={x:Type TextBox}}}"
               Foreground="LightGray"
               Background="White"
               Width="200" />
      </VisualBrush.Visual>
    </VisualBrush>
  </Style.Resources>
  <Style.Triggers>
    <Trigger Property="Text"
             Value="{x:Static sys:String.Empty}">
      <Setter Property="Background"
              Value="{DynamicResource CueBannerBrush}" />
    </Trigger>
    <Trigger Property="Text"
             Value="{x:Null}">
      <Setter Property="Background"
              Value="{DynamicResource CueBannerBrush}" />
    </Trigger>
    <Trigger Property="IsKeyboardFocused"
             Value="True">
      <Setter Property="Background"
              Value="White" />
    </Trigger>
  </Style.Triggers>
</Style>

添付プロパティ:

    public class CueBannerTextBox
{
    public static String GetCaption(DependencyObject obj)
    {
        return (String)obj.GetValue(CaptionProperty);
    }

    public static void SetCaption(DependencyObject obj, String value)
    {
        obj.SetValue(CaptionProperty, value);
    }

    public static readonly DependencyProperty CaptionProperty =
        DependencyProperty.RegisterAttached("Caption", typeof(String), typeof(CueBannerTextBox), new UIPropertyMetadata(null));
}

使用法:

<TextBox x:Name="txtProductInterfaceStorageId" 
                 EnhancedControls:CueBannerTextBox.Caption="myCustomCaption"
                 Width="200" 
                 Margin="5" 
                 Style="{StaticResource CueBannerTextBoxStyle}" />

テキストボックスを作成するときにビジュアルブラシで使用されるテキストプロンプトを定義できるという考えですが、バインディングエラーが発生しています:

System.Windows.Data Error: 4 : Cannot find source for binding with reference 'RelativeSource FindAncestor, AncestorType='System.Windows.Controls.TextBox', AncestorLevel='1''. BindingExpression:Path=(0); DataItem=null; target element is 'Label' (Name=''); target property is 'Content' (type 'Object')

スタイルで Label.Content プロパティをハードコーディングするだけで、コードは正常に機能します。

何か案は?

4

2 に答える 2

2

ここでの問題は、Style動作方法に関係しています。基本的に、の 1 つの「コピー」Styleが作成され (最初の参照時に)、その時点で、これを適用する複数のTextBoxコントロールが存在する可能性があります。 StyleRelativeSource?

(おそらく) 答えは、 a のTemplate代わりに aを使用するStyleことです。コントロールまたはデータ テンプレートを使用すると、 のビジュアル ツリーにアクセスでき、TemplatedParent必要な場所に移動できます。

編集:さらに考えてみると、ここで間違っている可能性があります...コンピューターの前に戻ったときに簡単なテストハーネスをまとめて、これを証明/反証できるかどうかを確認します.

さらに編集: 私が最初に言ったことは間違いなく「真実」でしたが、それはあなたの問題ではありません。ラウルが言ったこと:ビジュアルツリーは正しい:

  • BackgroundのプロパティをTextBoxインスタンスVisualBrushに設定しています。
  • そのVisualブラシの は、コントロールのビジュアル ツリーにマップされません。
  • その結果、その {RelativeSource FindAncestor}ビジュアルの親が null になるため、ナビゲーションは失敗します。
  • これは、 aStyleまたは aとして宣言されているかどうかに関係なく当てはまりControlTemplateます。
  • ElementNameそうは言っても、定義の再利用性が低下するため、間違いなく依存することは理想的ではありません。

じゃあ何をすればいいの?

含まれているブラシに適切な継承コンテキストをマーシャリングする方法を考えようとして、一晩中頭を悩ませていましたが、ほとんど成功していません...しかし、この超ハッキーな方法を思いつきました:

まず、ヘルパー プロパティ (注: 通常、この方法でコードをスタイルすることはありませんが、スペースを節約しようとしています):

public class HackyMess 
{
    public static String GetCaption(DependencyObject obj)
    {
        return (String)obj.GetValue(CaptionProperty);
    }

    public static void SetCaption(DependencyObject obj, String value)
    {
        Debug.WriteLine("obj '{0}' setting caption to '{1}'", obj, value);
        obj.SetValue(CaptionProperty, value);
    }

    public static readonly DependencyProperty CaptionProperty =
        DependencyProperty.RegisterAttached("Caption", typeof(String), typeof(HackyMess),
            new FrameworkPropertyMetadata(null));

    public static object GetContext(DependencyObject obj) { return obj.GetValue(ContextProperty); }
    public static void SetContext(DependencyObject obj, object value) { obj.SetValue(ContextProperty, value); }

    public static void SetBackground(DependencyObject obj, Brush value) { obj.SetValue(BackgroundProperty, value); }
    public static Brush GetBackground(DependencyObject obj) { return (Brush) obj.GetValue(BackgroundProperty); }

    public static readonly DependencyProperty ContextProperty = DependencyProperty.RegisterAttached(
        "Context", typeof(object), typeof(HackyMess),
        new FrameworkPropertyMetadata(default(HackyMess), FrameworkPropertyMetadataOptions.OverridesInheritanceBehavior | FrameworkPropertyMetadataOptions.Inherits));
    public static readonly DependencyProperty BackgroundProperty = DependencyProperty.RegisterAttached(
        "Background", typeof(Brush), typeof(HackyMess),
        new UIPropertyMetadata(default(Brush), OnBackgroundChanged));

    private static void OnBackgroundChanged(DependencyObject obj, DependencyPropertyChangedEventArgs args)
    {
        var rawValue = args.NewValue;
        if (rawValue is Brush)
        {
            var brush = rawValue as Brush;
            var previousContext = obj.GetValue(ContextProperty);
            if (previousContext != null && previousContext != DependencyProperty.UnsetValue)
            {
                if (brush is VisualBrush)
                {
                    // If our hosted visual is a framework element, set it's data context to our inherited one
                    var currentVisual = (brush as VisualBrush).GetValue(VisualBrush.VisualProperty);
                    if(currentVisual is FrameworkElement)
                    {
                        (currentVisual as FrameworkElement).SetValue(FrameworkElement.DataContextProperty, previousContext);
                    }
                }
            }
            // Why can't there be just *one* background property? *sigh*
            if (obj is TextBlock) { obj.SetValue(TextBlock.BackgroundProperty, brush); }
            else if (obj is Control) { obj.SetValue(Control.BackgroundProperty, brush); }
            else if (obj is Panel) { obj.SetValue(Panel.BackgroundProperty, brush); }
            else if (obj is Border) { obj.SetValue(Border.BackgroundProperty, brush); }
        }
    }
}

そして今、更新された XAML:

<Style x:Key="CueBannerTextBoxStyle"
       TargetType="{x:Type TextBox}">
  <Style.Triggers>
    <Trigger Property="TextBox.Text"
             Value="{x:Static sys:String.Empty}">
      <Setter Property="local:HackyMess.Background">
        <Setter.Value>
          <VisualBrush AlignmentX="Left"
                       AlignmentY="Center"
                       Stretch="None">
            <VisualBrush.Visual>
              <Label Content="{Binding Path=(local:HackyMess.Caption)}"
                     Foreground="LightGray"
                     Background="White"
                     Width="200" />
            </VisualBrush.Visual>
          </VisualBrush>
        </Setter.Value>
      </Setter>
    </Trigger>
    <Trigger Property="IsKeyboardFocused"
             Value="True">
      <Setter Property="local:HackyMess.Background"
              Value="White" />
    </Trigger>
  </Style.Triggers>
</Style>
<TextBox x:Name="txtProductInterfaceStorageId"
         local:HackyMess.Caption="myCustomCaption"
         local:HackyMess.Context="{Binding RelativeSource={RelativeSource Self}}"
         Width="200"
         Margin="5"
         Style="{StaticResource CueBannerTextBoxStyle}" />
<TextBox x:Name="txtProductInterfaceStorageId2"
         local:HackyMess.Caption="myCustomCaption2"
         local:HackyMess.Context="{Binding RelativeSource={RelativeSource Self}}"
         Width="200"
         Margin="5"
         Style="{StaticResource CueBannerTextBoxStyle}" />
于 2012-11-29T19:55:45.777 に答える
1

問題は、 のLabel内部がVisualBrushの視覚的な子ではTextBoxないことです。これが、バインディングが機能しない理由です。その問題に対する私の解決策は、ElementNameバインディングを使用することです。しかし、作成しているビジュアル ブラシはStyleのディクショナリ リソース内にありElementName、要素 ID が見つからないため、バインディングは機能しません。その解決策はVisualBrush、グローバル ディクショナリ リソースに を作成することです。の宣言については、この XAML コードを参照してくださいVisualBrush

<Window.Resources>
  <VisualBrush x:Key="CueBannerBrush"
               AlignmentX="Left"
               AlignmentY="Center"
               Stretch="None">
    <VisualBrush.Visual>
      <Label Content="{Binding Path=(EnhancedControls:CueBannerTextBox.Caption), ElementName=txtProductInterfaceStorageId}"
             Foreground="#4F48DD"
             Background="#B72121"
             Width="200"
             Height="200" />
    </VisualBrush.Visual>
  </VisualBrush>
  <Style x:Key="CueBannerTextBoxStyle"
         TargetType="{x:Type TextBox}">
    <Style.Triggers>
      <Trigger Property="Text"
               Value="{x:Static System:String.Empty}">
        <Setter Property="Background"
                Value="{DynamicResource CueBannerBrush}" />
      </Trigger>
      <Trigger Property="Text"
               Value="{x:Null}">
        <Setter Property="Background"
                Value="{DynamicResource CueBannerBrush}" />
      </Trigger>
      <Trigger Property="IsKeyboardFocused"
               Value="True">
        <Setter Property="Background"
                Value="White" />
      </Trigger>
    </Style.Triggers>
  </Style>
</Window.Resources>

このコードは機能するはずです。これ以上コードを変更する必要はないので、すべてのコードを書き直すわけではありません。

この解決策がうまくいくことを願っています...

于 2012-11-29T20:29:39.913 に答える