Silverlight DataGrid依儲存格值決定顏色的三種方法
| | | 2 | |
在使用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?WT.mc_id=DOP-MVP-37580"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml?WT.mc_id=DOP-MVP-37580"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008?WT.mc_id=DOP-MVP-37580"
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?WT.mc_id=DOP-MVP-37580"
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 + IValueConverterdg1.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;
#endregiondg3.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/dCFcBYpublic 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 CustDataColumnpublic 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);
//示範在程式端設定Styletb.TextAlignment = TextAlignment.Center;
return tb;}
}
#endregion}

【延伸閱讀】
Comments
# by Alan
黑大, 您看保哥都出了一本MVC的書了 您有沒有考慮也出一本jQuery & Silverlight的書呢 我們一定情義相挺
# by 瑜珈服
是要出本書分享下!