如何绑定到Style.Resource中的附加属性? [英] How can I bind to an attached property in a Style.Resource?

查看:57
本文介绍了如何绑定到Style.Resource中的附加属性?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在尝试使用附加属性在TextBox的背景中创建一个提示文本标签,但是我无法解析样式资源中与文本标题的绑定:

I'm trying to create a prompt text label in the background of a TextBox using attached properties, but I can't resolve the binding to the text caption in a style resource:

样式定义:

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

这个想法是,您可以定义创建文本框时在视觉笔刷中使用的文本提示,但是我遇到了绑定错误:

The idea is that you can define the text prompt used in the visual brush when you create the textbox, but I'm getting a binding error:

System.Windows.Data错误:4:找不到参考'RelativeSource FindAncestor,AncestorType ='System.Windows.Controls.TextBox',AncestorLevel ='1''的绑定源.BindingExpression:Path =(0);DataItem = null;目标元素是'标签'(名称='');目标属性为内容"(类型为对象")

如果我只是硬编码样式中的Label.Content属性,则代码可以正常工作.

The code works fine if I just hardcode the Label.Content property in the style.

有什么想法吗?

推荐答案

这里的问题与 Style 的工作方式有关:基本上,的一个副本"样式将被创建(首次引用),届时,您可能希望将多个 Style 应用于多个 TextBox 控件-它将使用哪个用于RelativeSource吗?

The problem here has to do with the way Style works: basically, one "copy" of the Style will be created (at first reference), and at that point, there may be multiple TextBox controls you want this Style applied to - which one will it use for the RelativeSource?

(可能的)答案是使用 Template 而不是 Style -通过控件或数据模板,您将能够访问 TemplatedParent ,它应该可以让您到达需要的位置.

The (probable) answer is to use a Template instead of a Style - with a control or data template, you'll be able to access the visual tree of the TemplatedParent, and that should get you where you need to be.

再想一想,我在这里可能是不正确的……当我回到电脑前时,我将整理一个快速的测试工具,看看是否可以证明/证明这一点.

On further thought, I may be incorrect here...I'll throw together a quick test harness when I'm back in front of a computer and see if I can prove/disprove this.

进一步虽然我最初所说的可以说是真实",但这不是您的问题;劳尔所说的:视觉树是正确的:

FURTHER While what I originally said was arguably "true", that's not your problem; What Raul said re: the visual tree is correct:

  • 您要将 TextBox 上的 Background 属性设置为 VisualBrush 实例.
  • 该笔刷的 Visual 不会映射到控件的Visual Tree中.
  • 结果,任何 {RelativeSource FindAncestor} 导航将失败,因为该视觉对象的父级将为空.
  • 无论是将其声明为 Style 还是将 ControlTemplate 声明,都是如此.
  • 所说的话,依靠 ElementName 绝对是不理想的,因为它降低了定义的可重用性.
  • You are setting the Background property on the TextBox to a VisualBrush instance.
  • The Visual of that brush is not mapped into the Visual Tree of the control.
  • As a result, any {RelativeSource FindAncestor} navigation will fail, as the parent of that visual will be null.
  • This is the case regardless of whether it is declared as a Style or a ControlTemplate.
  • All that said, relying on ElementName definitely is non-ideal, as it reduces the reusability of the definition.

那该怎么办?

我一直在动脑筋,一整夜都想着想办法将适当的继承上下文编组到所包含的笔刷中,但收效甚微……我确实提出了这个超级黑客>方式,但是:

I've been wracking my brain overnight trying to think of a way to marshal over the proper inheritance context to the contained brush, with little success...I did come up with this super-hacky way, however:

首先,helper属性(注意:我通常不以这种方式设置代码样式,而是试图节省空间):

First, the helper property (note: I don't usually style my code this way, but trying to save space):

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:

And now the updated 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}" />

这篇关于如何绑定到Style.Resource中的附加属性?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆