摘要:Silverlight DataGrid依儲存格值決定顏色的三種方法
在使用Silverlight DataGrid時,很常遇到一項需求--要能依儲存格值決定不同的文字顏色或背景色。起初不熟WPF及Silverlight Binding及Template運作原理,著實花了一些功夫摸索,這裡整理我找出的三種不同做法,一方面備忘,另一方面歡迎大家指教。
在以下的Silverlight程式中,放了三個DataGrid: dg1, dg2, dg3。資料來源為多筆分數資料,每筆記錄各有ScoreA與ScoreB兩個分數,在顯示時,希望做到分數>0時文字為綠色,若為負數則以紅色顯示。dg1,2,3各使用不同的方法達成依分數正負變色的效果:
dg1: 使用DataGridTemplate,放入TextBlock,Foregroud屬性Binding至ScoreA/B,但加掛IValueConverter將數字轉為紅色或綠色
dg2: 使用DataGridTextColumn,在DataGrid.LoadingRow事件中,利用DataGridTextColumn.GetCellContent(DataGridRowEventArgs.Row)的方式取出儲存格中的視覺元素(對DataGridTextColumn而言,就是TextBlock),將其轉型為TextBlock,DataGridRowEventArgs.Row.DataContext可取得資料物件,用Reflection的技巧取出ScoreA或ScoreB,再依其正負調整TextBlock的Foreground屬性。Reflection的地方可以用switch或if配合Hardcoding取代(例如: if (c.Header.ToString() == "ScoreA") v = sd.ScoreA;),或者可以用Binding + Converter方式將前景色跟資料屬性值綁在一起(此做法dg3會示範)。
dg3: 自訂一個ScoreColumn,繼承自DataGridTextColumn,但覆寫GenerateElement(),傳回TextBlock前,為Foreground屬性加上Binding,使其能隨資料值的正負做切換。另外,GenerateElement()可以動態傳回要顯示的視覺元素組合,可以變化出許多有趣的玩法。
<UserControl x:Class="CustCellLab.MainPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d" xmlns:local="clr-namespace:CustCellLab"
d:DesignHeight="300" d:DesignWidth="400"
xmlns:sdk="http://schemas.microsoft.com/winfx/2006/xaml/presentation/sdk"
Loaded="UserControl_Loaded">
<Grid x:Name="LayoutRoot" Background="White">
<Grid.RowDefinitions>
<RowDefinition Height="*" />
<RowDefinition Height="*" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<Grid.Resources>
<local:ScoreColorConverter x:Key="ScoreColorConverter" />
<Style x:Key="CenterText" TargetType="TextBlock">
<Setter Property="TextAlignment" Value="Center" />
</Style>
</Grid.Resources>
<sdk:DataGrid Margin="2" Name="dg1" Grid.Row="0" AutoGenerateColumns="False">
<sdk:DataGrid.Columns>
<sdk:DataGridTextColumn Header="SN" Binding="{Binding SN}"/>
<sdk:DataGridTemplateColumn Header="ScoreA">
<sdk:DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBlock Text="{Binding ScoreA}"
Foreground="{Binding ScoreA, Converter={StaticResource ScoreColorConverter}}"
TextAlignment="Center" />
</DataTemplate>
</sdk:DataGridTemplateColumn.CellTemplate>
</sdk:DataGridTemplateColumn>
<sdk:DataGridTemplateColumn Header="ScoreB">
<sdk:DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBlock Text="{Binding ScoreB}"
Foreground="{Binding ScoreB, Converter={StaticResource ScoreColorConverter}}"
TextAlignment="Center" />
</DataTemplate>
</sdk:DataGridTemplateColumn.CellTemplate>
</sdk:DataGridTemplateColumn>
</sdk:DataGrid.Columns>
</sdk:DataGrid>
<sdk:DataGrid Margin="2" Name="dg2" Grid.Row="1" AutoGenerateColumns="False">
<sdk:DataGrid.Columns>
<sdk:DataGridTextColumn Header="SN" Binding="{Binding SN}" />
<sdk:DataGridTextColumn Header="ScoreA" Binding="{Binding ScoreA}"
ElementStyle="{StaticResource CenterText}"/>
<sdk:DataGridTextColumn Header="ScoreB" Binding="{Binding ScoreB}"
ElementStyle="{StaticResource CenterText}" />
</sdk:DataGrid.Columns>
</sdk:DataGrid>
<sdk:DataGrid Margin="2" Name="dg3" Grid.Row="2" AutoGenerateColumns="False">
<sdk:DataGrid.Columns>
<sdk:DataGridTextColumn Header="SN" Binding="{Binding SN}" />
<local:ScoreColumn Header="ScoreA" Binding="{Binding ScoreA}" />
<local:ScoreColumn Header="ScoreB" Binding="{Binding ScoreB}" />
</sdk:DataGrid.Columns>
</sdk:DataGrid>
</Grid>
</UserControl> 後端程式碼如下:using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;
using System.Windows.Data;
using System.Globalization;
using System.Reflection;
namespace CustCellLab
{public partial class MainPage : UserControl{public MainPage()
{InitializeComponent();}List<SimData> data = new List<SimData>();
private void UserControl_Loaded(object sender, RoutedEventArgs e){//以亂數摸擬資料
Random rnd = new Random();
for (int i = 0; i < 100; i++){data.Add(new SimData()
{SN = i.ToString("00000"),
ScoreA = 100 - rnd.Next(199),ScoreB = 100 - rnd.Next(199)});}//方法1: 使用DataGridTemplateColumn + IValueConverter
dg1.ItemsSource = data;#region 方法2: 利用LoadingRow事件加工
SolidColorBrush normal = new SolidColorBrush(Colors.Green);
SolidColorBrush negative = new SolidColorBrush(Colors.Red);
dg2.LoadingRow += (s, o) =>{//o為DataGridRowEventArgs
SimData sd = o.Row.DataContext as SimData;
//巡過所有欄位,找出Score*
foreach (DataGridColumn c in dg2.Columns){if (c.Header.ToString().StartsWith("Score")){//動態取得TextBlock物件
TextBlock tb =c.GetCellContent(o.Row) as TextBlock;
//o.Row.DataContext就是SimData, 可以Hard-Coding去取sd.ScoreA/B
//這裡則示範用Reflection法取Binding目標欄位值
PropertyInfo pi = sd.GetType().GetProperty((c as DataGridTextColumn).Binding.Path.Path);
decimal v = Convert.ToDecimal(pi.GetValue(sd, null));tb.Foreground = (v >= 0) ? normal : negative;}}};dg2.ItemsSource = data;#endregion
dg3.ItemsSource = data;}}public class SimData{public string SN { get; set; }public decimal ScoreA { get; set; }public decimal ScoreB { get; set; }}#region CellTemplate & ValueConverter
//REF: http://bit.ly/dCFcBY
public class ScoreColorConverter : IValueConverter{static SolidColorBrush NormalColor =
new SolidColorBrush(Colors.Green);
static SolidColorBrush NegColor =
new SolidColorBrush(Colors.Red);
//依數字正負採用不同顏色
public object Convert(object value,Type targetType,object parameter,
CultureInfo culture){decimal score = System.Convert.ToDecimal(value);return score >= 0 ? NormalColor : NegColor;
}public object ConvertBack(object value,Type targetType,object parameter,
CultureInfo culture){throw new NotSupportedException();}}#endregion
#region CustDataColumn
public class ScoreColumn : DataGridTextColumn{static ScoreColorConverter scc = new ScoreColorConverter();protected override FrameworkElement GenerateElement(DataGridCell cell, object dataItem)
{//GenerateElement可以用來任意組裝要呈現的元素, 很有彈性
TextBlock tb =base.GenerateElement(cell, dataItem) as TextBlock;//此例用Binding.Converter的方法動態換色
//若不用Converter,用轉型或Refelection取出dataItem的值做判斷亦可
Binding b =new System.Windows.Data.Binding(this.Binding.Path.Path);b.Converter = scc;tb.SetBinding(TextBlock.ForegroundProperty, b);//示範在程式端設定Style
tb.TextAlignment = TextAlignment.Center;return tb;
}}#endregion
}