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