說到反射嘛,估計各位不陌生,盡管很多人不知道到底是什麼,當然也有人將其看作是“反編譯” ,有點像吧,但不能說是反編譯,雖然有個“反”字,但沒有“編譯”。
所以,我給反射下了 這樣一個概述,准確與否,也不清楚:
反射可以動態獲取程序集信息,或程序集中的類型信息 ,以方便動態調用。
動態調用的好處是節約資源,通常情況下,我們添加程序集引用會在項目 的引用中加入,這意味著只要應用程序運行了,這些程序集就會被加載,不管你是否需要調用。對於調 用較少的程序集,如果考慮在調用時才加載,調用完了就釋放,這樣會在一定程度上節約內存占用,當 然這不是優化代碼的唯一方法,其實代碼優化是沒有固定規則的,綜合而靈活去運用各種技巧都可以優 化代碼和提高性能。
反射重點在於“動態”二字上,我忽然想到了數據綁定,於是我想到一些 東西,平時我們做UI與數據的綁定時,多數情況下是已知數據項目類型有哪些公共屬性,進而綁定到對 象的某個公共屬性上。那麼試想一下,假如我們事先不知道來自數據源的對象是什麼結構,也不清楚它 有哪些公共屬性,這樣數據綁定起來是不是會有麻煩呢?
這顯然是有麻煩的,而且,常規的思 路是很難解決的,那麼這時候我們是不是想到了反射呢?不管來自數據源的是什麼樣的對象類型,我只 要通過反射將它的所有公共屬性都找出來,然後再取出對應屬性的值,再動態生成UI界面。若能如此, 是不是很爽呢?
我這個人有個缺點,就是一旦想到什麼鬼點子,就會迫不及待地試試,故二話 不說,馬上扔掉手頭上的東西,打開電腦,啟動VS,就試著Coding起來了,這一Code還真沒白費力氣, 總算Code出我的預期效果。
以下是我的大致思路:
首先,編寫一個類,提供操作,可以 將任意類型對象的列表轉換成WPF中的UI元素,接著再把轉換出來的UI元素列表當作數據源,賦給 ListBox的ItemsSource屬性,完成綁定。
這樣做的好處就是,不管來自數據源的對象列表中的 類有幾個公共屬性,只要能把它反射出來就行了。
下面是該轉換類的代碼。
using System; using System.Collections; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; using System.Reflection; using System.Windows; using System.Windows.Controls; using System.Windows.Media; using System.ComponentModel.DataAnnotations; namespace MyApp { public class ObjectsConvertToUIFrmws { public IList<FrameworkElement> BuildUIList(IList objs) { List<FrameworkElement> uiList = new List<FrameworkElement>(); foreach (var obj in objs) { FrameworkElement fe = BuildUICore(obj); uiList.Add(fe); } return uiList; } private PropertyInfo[] GetPropertiesFromObj(object o) { return o.GetType().GetProperties(BindingFlags.Public | BindingFlags.Instance); } private Panel BuildUICore(object obj) { if (obj == null) return null; PropertyInfo[] pis = GetPropertiesFromObj(obj); Grid gridRoot = new Grid(); // 有多少個屬性就加入多少行,每行顯示一個 gridRoot.ColumnDefinitions.Add(new ColumnDefinition { Width = GridLength.Auto }); gridRoot.ColumnDefinitions.Add(new ColumnDefinition { Width = new GridLength(1, GridUnitType.Star) }); for (int i = 0; i < pis.Length; i++) { gridRoot.RowDefinitions.Add(new RowDefinition() { Height = GridLength.Auto }); } for (int n = 0; n < pis.Length;n++ ) { var attrs = pis[n].GetCustomAttributes(typeof(DisplayAttribute)).ToArray(); // 獲取屬性的值 object vl = pis[n].GetValue(obj); if (vl is Color) { // 如果字段表示顏色,則設置為容器的背景色 Color bgColor = (Color)vl; gridRoot.Background = new SolidColorBrush(bgColor); } else { TextBlock tbDisplayName = new TextBlock(); if (attrs != null && attrs.Count() > 0) { DisplayAttribute dispattr = attrs[0] as DisplayAttribute; tbDisplayName.Text = dispattr.Name; } else { tbDisplayName.Text = "未知字段"; } gridRoot.Children.Add(tbDisplayName); Grid.SetRow(tbDisplayName, n); Grid.SetColumn(tbDisplayName, 0); TextBlock tbValue = new TextBlock(); // 如果屬性類型為日期時間,則轉換字符串格式 if (vl is DateTime) { tbValue.Text = ((DateTime)vl).ToString("yyyy-MM-dd HH:mm:ss"); } else if (vl is double) { // 如果是雙精度類型,則要保留三位小數 tbValue.Text = ((double)vl).ToString("N3"); } else { tbValue.Text = vl.ToString(); } gridRoot.Children.Add(tbValue); Grid.SetRow(tbValue, n); Grid.SetColumn(tbValue, 1); } } return gridRoot; } } }
通常情況下,在UI上顯示的字符串最好不是屬性的名字,因為用戶看起來不方便,也可能看 不懂,所以,這裡要求一個規范,就是在定義數據實體類時,為每個屬性加一個DisplayAttribute特性 ,並將其Name屬性設置為要在UI上呈現的字符串。在反射過程中,會取出該特性,並訪問其Name屬性的 值,然後將這個字符串賦給UI象。
比如,現在,我們可以隨便定義一個類來做測試。
public class TestDataItem { [Display(Name = "名字:")] public string Name { get; set; } [Display(Name = "年齡:")] public int Age { get; set; } [Display(Name = "城市:")] public string City { get; set; } public Color BackGround { get; set; } }
由於,在前面的轉換類中,只要碰到是Color結構類型的屬性,就不將其顯示在UI上,而是 作為UI面板容器(Grid對象)的背景色,因此,這裡定義數據對象類的時候,就不用為類型為Color的屬 性添加DisplayAttribute特性了。
你在測試的時候,不一定要TestDataItem類那樣定義,你可 以任意定義實體類,然後通過前面寫的轉換類,將期轉換為UI對象。
public MainWindow() { InitializeComponent(); List<TestDataItem> list = new List<TestDataItem>(); list.Add(new TestDataItem { Name = "abc", Age = 12, City = "大連", BackGround = Colors.Yellow }); list.Add(new TestDataItem { Name= "def", Age = 20, City="上海", BackGround = Colors.Pink }); list.Add(new TestDataItem { Name = "gao", Age = 60, City="珠海", BackGround = Colors.SkyBlue }); ObjectsConvertToUIFrmws cf = new ObjectsConvertToUIFrmws(); this.lbList.ItemsSource = cf.BuildUIList(list); }
你看,不管定義的實體是什麼結構,都可以動態生成UI元素,如此是不是很方便呢?
上面的例子最後得到如下圖所示的結果。
最後,我聲明一下,我 沒有說非得用這種方法來綁定數據不可,我只是結合了反射的用途而已,不要盲目使用,但我相信肯定 有用,你在使用時不妨考慮一下,哪些情況下可以使用這種思路。
查看本欄目