在 Silverlight 5 中,如何将每个 DataGrid 的行背景颜色绑定到它的绑定项的属性之一?

In Silverlight 5, how do I bind each DataGrid's row background color to one of its bound item's properties?

Please note that this question is specific to Silverlight 5. There are lots of similar questions floating around, but often they are about WPF or previous Silverlight versions. Others target Silverlight 5, but are about controls other than DataGrid. I have been unable to find an existing question that asks for exactly the same thing as this one; at least none with an answer that works for me.

假设我有这个非常简单的 DataGrid(XAML 和 C# 代码),它绑定到具有两个属性的 INotifyPropertyChanged 类型的集合,SelectedText:

我想将每一行的背景颜色绑定到其数据项的 Selected 属性(使用一些 bool-to-Brush 转换器)。通常的行状态(正常、选定、鼠标悬停等)视觉效果和过渡不应受到影响;也就是说,正常行将被着色,交替行将稍微亮一些,鼠标悬停的行将稍微暗一些,选定的行将比这更暗,等等。

在其他地方看到的一些明显的解决方案:

我已经搜索了几个小时的解决方案,周围有许多类似的问题,但 none 的答案似乎适用于 Silverlight 5,或者它们不起作用。似乎有几种方法可以解决这个问题:

  1. 覆盖 DataGridRow 控件模板 并绑定其 BackgroundRectangleBackground 属性:

    <sdk:DataGrid …>
      <sdk:DataGrid.RowStyle>
        <Style TargetType="sdk:DataGridRow">
          <Setter Property="Template">
            <Setter.Value>
              <ControlTemplate>
                …
                <Rectangle x:Name="BackgroundRectangle" …
                           Background="{Binding Selected, Converter={StaticResource boolToBrush}}" />
                …
              </ControlTemplate>
            </Setter.Value>
          </Setter>
        </Style>
      </sdk:DataGrid.RowStyle>
      …
    </sdk:DataGrid>
    

    这最初有效,并且正确呈现视觉状态(因为 DataGrid 视觉状态仅影响 BackgroundRectangle 的不透明度;有关详细信息,请参阅 the default control template definition)。

    问题在于,当绑定项的 Selected 属性更改值时,行背景色不会更改。就好像 {Binding} 有一个 Mode=OneTime.

    另外,我了解到应该避免在控件模板中进行数据绑定。 TemplateBindings 在控件模板中很好,但常规的 Bindings 可能不属于这里。

  2. 将所有列更改为 <sdk:DataGridTemplateColumn> 并使用数据模板:

    <sdk:DataGrid …>
      <sdk:DataGrid.Columns>
        <sdk:DataGridTemplateColumn Header="Selected">
          <sdk:DataGridTemplateColumn.CellTemplate>
            <DataTemplate>
              <Grid Background="{Binding Selected, Converter={StaticResource boolToBrush}}">
                <CheckBox IsChecked="{Binding Selected}" />
              </Grid>
            </DataTemplate>
          </sdk:DataGridTemplateColumn.CellTemplate>
          … <!-- repeat the above for each column -->
        </sdk:DataGridTemplateColumn>
      </sdk:DataGrid.Columns>
      …
    </sdk:DataGrid>
    

    这更糟。单元格获得正确的背景颜色,但不再正确应用数据网格的各种视觉状态。此外,每个列都必须变成模板列。第三,当绑定属性改变值时,背景颜色不会更新。

  3. 使用 Expression Blend 数据触发器(来自 System.Windows.InteractivityMicrosoft.Expression.Interactivity.Core 命名空间的类型)。老实说,我一直无法弄清楚它应该如何工作。

问题:

我真的不想在我手动执行此操作的地方求助于命令式代码隐藏。但是我怎样才能在 XAML 中以适当的方式做我想做的事情,同时也尊重 DataGridRow 视觉状态和行为,并在绑定 属性 更改时更新行背景?

谁能给我一个 XAML 示例,说明如何以这种方式将每一行的背景颜色绑定到其绑定项的属性之一?

XAML 和上述数据网格的 C# 代码:

<UserControl x:Class="SomeApplication.MainView"
  …
  xmlns:local="clr-namespace:SomeApplication"
  xmlns:sdk="clr-namespace:System.Windows.Controls;assembly=System.Windows.Controls.Data">

  <!-- sample data context -->
  <UserControl.DataContext>
    <local:MainViewModel>
      <local:MainViewModel.Things>
        <local:ThingViewModel Text="A" />
        <local:ThingViewModel Text="B" />
        <local:ThingViewModel Text="C" />
        <local:ThingViewModel Text="D" Selected="True" />
        <local:ThingViewModel Text="E" />
      </local:MainViewModel.Things>
    </local:MainViewModel>
  </UserControl.DataContext>

  <Grid>
    <sdk:DataGrid ItemsSource="{Binding Things}" AutoGenerateColumns="False">
      <sdk:DataGrid.Columns>
        <sdk:DataGridCheckBoxColumn Header="Selected" Binding="{Binding Selected}" />
        <sdk:DataGridTextColumn Header="Text" Binding="{Binding Text}" />
      </sdk:DataGrid.Columns>
    </sdk:DataGrid>
  </Grid>
</UserControl>

namespace SomeApplication
{
    public interface IMainViewModel
    {
        ObservableCollection<ThingViewModel> Things { get; }
    }

    public interface IThingViewModel : INotifyPropertyChanged
    {
        bool Selected { get; set; }
        string Text { get; set; }
    }

    // straightforward implementations, omitted for brevity's sake:
    public partial class MainViewModel : IMainViewModel { }
    public partial class ThingViewModel : IThingViewModel { }
}

似乎有效的一件事是覆盖控件模板并将 Expression Blend 数据触发器放入 BackgroundRectangle

DataGridRow 的默认控件模板开始(您可以找到 on the MSDN page 'DataGrid Styles and Templates')。唯一需要更改的是 <Rectangle x:Name="BackgroundRectangle">:

<!-- 
  xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity" 
  xmlns:ei="clr-namespace:Microsoft.Expression.Interactivity.Core;assembly=Microsoft.Expressions.Interactions" 
-->

<sdk:DataGrid …>
  <sdk:DataGrid.RowStyle>
    <Style TargetType="sdk:DataGridRow">
      <Setter Property="Template">
        <Setter.Value>
          <ControlTemplate>
            … <!-- use DataGridRow standard control template, except for this: -->
            <Rectangle x:Name="BackgroundRectangle" …
                       Background="{Binding Selected, Converter={StaticResource boolToBrush}}">
              <i:Interaction.DataTriggers>
                <ei:PropertyChangedTrigger Binding="{Binding Selected}" Value="True">
                  <ei:PropertyChangedTrigger.Actions>
                    <ei:ChangePropertyAction TargetName="BackgroundRectangle"
                                             PropertyName="Fill" 
                                             Value="{Binding Selected, Converter={StaticResource boolToBrush}}" />
                  </ei:PropertyChangedTrigger.Actions>
                </ei:PropertyChangedTrigger>
              </i:Interaction.DataTriggers>
            <Rectangle>
            …
          </ControlTemplate>
        </Setter.Value>
      </Setter>
    </Style>
  </sdk:DataGrid.RowStyle>
  …
</sdk:DataGrid>

由于一行的背景矩形在控件模板内部,而且背景应该随着数据而变化,看来在控件模板内部放置数据绑定实际上是不可避免的。