在使用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 瑜珈服

是要出本書分享下!

Post a comment