2009/12/31

WPF Frame.NavigationFailed 正確捕捉方式

我們的程式用到了 Frame 當作瀏覽器視窗,可以接受使用者輸入的 URL 顯示網頁。但如果使用者隨便輸入或鍵入一個不存在的網址要求 (使用 frame.Navigate(Uri)),Frame 會跳出 WebException。

因為這樣會強制結束程式,所以我加個 try ... catch 來攔截,這下總沒有問題了吧?

try{
    frame.Navigate(new Uri(url));
}catch{
    // show something ...
}

結果程式一跑,相同的 Exception 一樣憑空跳出,讓我丈二金剛摸不著頭腦,究竟是怎麼一回事呢?查了MSDN發現,原來 Frame 有專屬的 NavigationFailed 事件,要撰寫事件來抓這個錯誤才行,Navigate 方法本身不丟出錯誤的。

frmBrowser.NavigationFailed += new NavigationFailedEventHandler(this.NavigationFailedEventHandler);

private void NavigationFailedEventHandler(object sender, NavigationFailedEventArgs e) {
    // show something ...
}

好,我按照 MSDN 的說明,用了事件捕捉,這下總沒事了吧?事情通常沒我們想的簡單,Exception 還是偷偷的跑出來打斷了我們的程式,怎麼會這樣?

注意看 MSDN,尋找可能的問題,後來發現事件的其中一個參數 NavigationFailedEventArgs 非常可疑,一看才知道原來它隱藏著本篇的解藥!其實就是它的 Handled 屬性。說明是這樣寫的:
當處理 NavigationFailed 以處理因巡覽失敗所擲回的例外狀況,而您也不想 WPF 繼續處理例外狀況時,應該將 Handled 屬性設定為 true。
private void NavigationFailedEventHandler(object sender, NavigationFailedEventArgs e) {
    // show something ...
    e.Handled = true; // 已處理,不再擲出錯誤
}

結果加了一行,例外就乖乖的不出現了。如此重要的參數竟然在事件裡或範例中隻字未提,如果沒有發現 NavigationFailedEventArgs 參數的玄機,我想會卡非常久。特此將此經驗公諸於世,希望能救到後來人。

WPF ListView + GridView Items.Add

我們的程式用到了 ListView,而且又用了 GridView 分欄,讓清單看起來比較美觀,結果就像是Windows檔案總管的詳細資料那樣。


<ListView x:Name="listView1" Margin="178,29,35,51" SelectionMode="Single">
    <ListView.View>
        <GridView>
            <GridViewColumn Header="FileName" Width="230" DisplayMemberBinding="{Binding Path=FileName}" />
            <GridViewColumn Header="Date" Width="140" DisplayMemberBinding="{Binding Path=Date}" />
        </GridView>
    </ListView.View>
</ListView>

但是加上了GridView的ListView,不再可以容易的以 物件名.Items.Add("String") 直接新增一條資料,這樣許多欄位的資料都會一樣。網路上的做法是使用資料繫結的方式,維護一個物件然後 Bind 上去即可,但是我還是習慣以程式動態新增,可不可以達成呢?當然可以。

因為是 WPF,所以沒有 Windows Form 的 ListViewItem 物件和物件名.SubItem 等屬性可以用。查了一下網路說明,需要建立一個物件而不是文字傳入 Items.Add()。

因為我是多欄,所以物件也要有多個屬性值,我建立一個結構來儲存:
private struct FileStructure{
    public string FileName { get; set; }
    public String Date { get; set; }
}
值得一提的是 { get; set; } 要加上,如果沒有加上這個繫結將會失敗。

然後在 WPF 的 GridView 的 DisplayMemberBinding 屬性設定 Binding,讓程式知道物件的哪個值對應到哪個欄位。請參考一開始的 WPF 原始碼。雖然我的方法還是有用到 Bind,但那是告訴 ListView 我的東西要怎樣呈現。

最後,以程式新增:

listView1.Items.Add(new FileStructure{ FileName = "test.zip", Date = "2009/12/21" });
listView1.Items.Add(new FileStructure{ FileName = "test.rar", Date = "2009/12/22" });
...

這樣就可以動態新增ListView的資料,而且每一欄的資料都正確的填進去了。
---
參考文獻:
http://stackoverflow.com/questions/1305406/wpf-listview-how-to-bind-single-items