當時並沒有想太多,因為該專案不算複雜,我只定義了一種叫 ItemBase 的類別來讓專案中所有的 GridView 做 Binding,直到後來我碰到一個狀況。
在最近我發現之前的寫法有個很大的問題,我每定義一種結構,就要為了那種結構生一個專門處理它的衍生自 GridView 的類別,原因在於 PrepareContainerForItemOverride 這個 Method 裡面我必須把 Item Object 做轉型才能拿到 Span 資訊,所以後來我定義了一個介面名為 ISpanItem 長下面這個樣子
public interface ISpanItem
{
int ItemRowSpan { get; }
int ItemColumnSpan { get; }
}
接著我只要讓所有的結構實作 ISpanItem 就可以利用同一個 GridView 做出 VariableSizedWrapGrid 效果,一開始我是這麼寫的
class NoSpanItemSizeException : Exception
{
public NoSpanItemSizeException() : base("must implement ISpanItem this interface") { }
}
public interface ISpanItem
{
int ItemRowSpan { get; }
int ItemColumnSpan { get; }
}
public class VSWGridView : GridView
{
protected override void PrepareContainerForItemOverride(DependencyObject element, object item)
{
base.PrepareContainerForItemOverride(element, item);
ISpanItem _spanItem = null;
try
{
_spanItem = (ISpanItem)item;
}
catch (Exception e) // 這個地方若只接 InvalidCastException 會更好
{
throw new NoSpanItemSizeException();
}
VariableSizedWrapGrid.SetRowSpan(element as UIElement, _spanItem.ItemRowSpan);
VariableSizedWrapGrid.SetColumnSpan(element as UIElement, _spanItem.ItemColumnSpan);
}
}
其實這樣的寫法沒什麼問題,只是我後來在想,真的有必要用一個 try catch 來避免非 ISpanItem 的介面嗎?
使用 (型別)物件 這種強制轉型的語法也一直是我的習慣,它的確只能 catch Exception 這種方式來知道轉型失敗,後來我查了一下,看來還有更好的寫法,所以我把這段程式碼改成這個樣子
public class VSWGridView : GridView
{
protected override void PrepareContainerForItemOverride(DependencyObject element, object item)
{
base.PrepareContainerForItemOverride(element, item);
int rowSpan = 1;
int colSpan = 1;
if (item is ISpanItem)
{
ISpanItem si = item as ISpanItem;
rowSpan = si.ItemRowSpan;
colSpan = si.ItemColumnSpan;
}
VariableSizedWrapGrid.SetRowSpan(element as UIElement, rowSpan);
VariableSizedWrapGrid.SetColumnSpan(element as UIElement, colSpan);
}
}
利用 is 來判斷該物件是否屬於該介面或該類型,並用 as 來做轉型,利用 as 來做轉型有個好處,它不會發生 Exception,所以對效能是有幫助的,若無法轉型的情況 si 就會是 null,所以我們可以判斷 si 在轉型後是否為 null 或像上面的範例一樣一開始就用 is 來判斷即可,此處我也把 throw Exception 遺棄了,讓 VSWGridView 可以通吃所有的結構,只是在沒有實作 ISpanItem 介面的狀況下就讓 VariableSize 失效如此而已。
沒有留言:
張貼留言