【问题标题】:How can I bind to an attached property in a Style.Resource?如何绑定到 Style.Resource 中的附加属性?
【发布时间】:2012-11-29 17:36:43
【问题描述】:

我正在尝试使用附加属性在 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 属性,代码就可以正常工作。

有什么想法吗?

【问题讨论】:

    标签: c# .net wpf xaml


    【解决方案1】:

    这里的问题与Style 的工作方式有关:基本上,将创建Style 的一个“副本”(在第一次引用时),此时可能会有多个@ 987654323@ 控件,您希望将此 Style 应用于 - 它将用于相对源?

    (可能的)答案是使用Template 而不是Style - 使用控件或数据模板,您将能够访问TemplatedParent 的可视化树,这应该可以帮助您你需要去的地方。

    编辑:进一步考虑,我可能在这里不正确......当我回到电脑前时,我会整理一个快速测试工具,看看我是否可以证明/反驳这一点。

    进一步编辑:虽然我最初所说的可以说是“正确的”,但这不是你的问题; Raul 所说的回复:视觉树是正确的:

    • 您正在将TextBox 上的Background 属性设置为VisualBrush 实例。
    • 那个画笔的Visual没有映射到控件的可视树中。
    • 因此,任何 {RelativeSource FindAncestor} 导航将失败,因为该视觉对象的父级将为空。
    • 无论是声明为Style 还是ControlTemplate,都是这种情况。
    • 综上所述,依赖 ElementName 绝对是不理想的,因为它会降低定义的可重用性。

    那么,该怎么办?

    我一夜之间绞尽脑汁想办法将正确的继承上下文编组到包含的画笔,但收效甚微...我确实想出了这个超级hacky 方式,然而:

    首先是 helper 属性(注意:我通常不会这样设置我的代码样式,但会尽量节省空间):

    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}" />
    

    【讨论】:

    • 好的,我会试试 - 如果你成功了,请告诉我!谢谢
    【解决方案2】:

    问题在于VisualBrush 中的Label 不是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>
    

    这段代码应该可以工作。不再需要更改代码,因此我不会重写所有代码。

    希望这个解决方案对你有用……

    【讨论】:

    • 看起来它只是将元素名称硬编码到样式中 - 这将如何重用?我只能有一个使用此解决方案的文本框?
    猜你喜欢
    • 2016-04-14
    • 2021-12-01
    • 2011-02-07
    • 1970-01-01
    • 1970-01-01
    • 2011-07-03
    • 1970-01-01
    • 1970-01-01
    • 2011-11-01
    相关资源
    最近更新 更多