关于c#:将触发器绑定到父集合中的元素

Bind trigger to element in parent collection

我有一个控件,它的依赖属性"IsLightOnVal"是这样定义的:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
// List of available states for this control
private ObservableCollection<CtlStateBool> m_IsLightOnVal;

[Category("Properties")]
public System.Collections.ObjectModel.ObservableCollection<CtlStateBool> IsLightOnVal
{
    get
    {
        if (m_IsLightOnVal == null)
            m_IsLightOnVal = new System.Collections.ObjectModel.ObservableCollection<CtlStateBool>();
        return m_IsLightOnVal;
    }
    set
    {
        if (m_IsLightOnVal != value)
        {
            m_IsLightOnVal = value;
            OnPropertyChanged("IsLightOnVal");
        }
    }
}

// IsLightOnVal dependency property.
public static readonly DependencyProperty IsLightOnValProperty =
        DependencyProperty.Register("IsLightOnVal", typeof(System.Collections.ObjectModel.ObservableCollection<CtlStateBool>), typeof(ButtonSimple), new UIPropertyMetadata(new System.Collections.ObjectModel.ObservableCollection<CtlStateBool>()));

在我的集合中,每个元素都包含一个字符串(State)和一个布尔值(Value)

我的控件样式在 ControlTemplate 中定义。

我想添加一个触发器,例如当我的集合中的第一个元素为真时,然后做一些事情。

我试过这个:

1
2
3
4
5
6
7
8
9
10
<Style x:Key="Btn_RADIO_VHF" TargetType="{x:Type ButtonSimple}">
   <Setter Property="Template">
      <Setter.Value>
         <ControlTemplate TargetType="{x:Type ButtonSimple}">
            <Canvas .../>
            <ControlTemplate.Triggers>
               <DataTrigger Value="True" Binding="{Binding IsLightOnVal[0].Value, RelativeSource={RelativeSource TemplatedParent}}">
                  <Setter Property="Fill" TargetName="pShowTouch" Value="{DynamicResource ShowTouch}"/>
               </DataTrigger>
            </ControlTemplate.Triggers>

我也尝试使用简单的触发器而不是 DataTrigger,但它似乎不支持绑定...

有人可以帮我吗?


你有很多问题。首先,您使用的是 TemplatedParent 的 RelativeSource,但这不是应用于模板内元素的绑定,因此您应该使用 Self。这样可以相对容易地修复:

1
<DataTrigger Value="True" Binding="{Binding Path=IsLightOnVal[0].Value, RelativeSource={RelativeSource Self}}">

其次,您已将此属性定义为 CLR 属性(具有自己的后备存储)和 DependencyProperty。如果属性被定义为 DP,那么框架将期望您使用 DP 来存储值。在您的代码中,您永远不会使用 SetValue 方法将集合实例实际存储在 DependencyObject 的后备存储中。所以有很多方法可以解决这个问题:

1) 移除 DP:

1
2
//public static readonly DependencyProperty IsLightOnValProperty =
//        DependencyProperty.Register("IsLightOnVal", typeof( System.Collections.ObjectModel.ObservableCollection<CtlStateBool> ), typeof( ButtonSimple ), new UIPropertyMetadata( new System.Collections.ObjectModel.ObservableCollection<CtlStateBool>() ) );

由于它不是 DP,尽管您无法在 setter 中对其进行设置,将其绑定到 VM 上的某些属性等,因此这可能不是最佳选择。

2) 将值存储在 DP 以及您的局部变量中:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public System.Collections.ObjectModel.ObservableCollection<CtlStateBool> IsLightOnVal
{
    get
    {
        if ( m_IsLightOnVal == null )
            this.SetValue(IsLightOnValProperty, m_IsLightOnVal = new System.Collections.ObjectModel.ObservableCollection<CtlStateBool>());
        return m_IsLightOnVal;
    }
    set
    {
        if ( m_IsLightOnVal != value )
        {
            this.SetValue( IsLightOnValProperty, m_IsLightOnVal = value );
            OnPropertyChanged("IsLightOnVal" );
        }
    }
}

我个人不喜欢这个选项。或者更具体地说,我认为在吸气剂中懒惰地分配自己的财产是不好的做法。这将在对象上设置一个本地值,如果有人实际上将其设置为较低的优先级,则该值可能会覆盖实际值(例如,在模板中定义了 this 的一个实例,并且在那里设置/绑定了属性)。如果您计划设计时支持,这可能会搞砸设计师。如果你确实走这条路,那么你真的应该在你的 DP 定义中添加一个 PropertyChangedHandler 并确保在那里设置你的 m_IsLightOnVal 成员变量,否则如果值是通过 DP 设置的(例如某人 - 包括WPF 框架 - 使用 SetValue 设置属性的值)。

3) 仅使用 GetValue/SetValue

1
2
3
4
5
public System.Collections.ObjectModel.ObservableCollection<CtlStateBool> IsLightOnVal
{
    get { return (System.Collections.ObjectModel.ObservableCollection<CtlStateBool>)this.GetValue(IsLightOnValProperty); }
    set { this.SetValue( IsLightOnValProperty, value ); }
}

我会推荐这种方法。是的,这意味着任何希望设置属性的人都必须定义集合的实例,但我认为这比设置自己的 DP 值可能遇到的问题更可取。请注意,如果您采用这条路线,那么您可能想要定义一个派生自 ObservableCollection 的非泛型集合类,以便有人可以在 xaml 中定义集合类的实例,尽管如果您希望它只是绑定到那么 this可能不是问题。从对其他回复的评论中,虽然听起来它可能是在 xaml 中设置的。


现在你的触发器永远不会被触发,因为 ObservableCollection 不支持所包含元素的属性更改通知。

您可以尝试实现支持 ChangeNotification 的 ObservableCollection 的特化,如这里所见,例如 Extending ObservableCollection

但是,将 ObservableCollection 的第一个值存储在 ViewModel/ 后面的代码中并将其设置为触发器的目标可能会更容易。


找到了:

1
2
3
<DataTrigger Binding="{Binding MyCollection[index].Value, RelativeSource={RelativeSource Self}}" Value="True">
    <Setter .../>
</DataTrigger>