Silverlight: 小心事件重覆註冊的陷阱

摘要:Silverlight: 小心事件重覆註冊的陷阱

只要是在 Silverlight 2.0 中要呼叫外部來源的通訊,幾乎都是要用非同步的作法,而最常和 Silverlight 通訊的,我想非 WCF 莫屬了,並且因為要處理非同步,所以我們可能會這樣寫:

// 由伺服器撈取目錄,透過 WCF Service 來做。
private void tvDirectory_SelectedItemChanged(object sender, RoutedEventArgs e)
{
    TreeViewItem item = this.tvDirectory.SelectedItem as TreeViewItem;

    if (!item.HasItems)
    {
        ServerDirectoryBrowserService.DirectoryInfo di =
            item.Tag as ServerDirectoryBrowserService.DirectoryInfo;

        if (di.HasSubdirectory)
        {
            this.UpdateState("Loading directory information...");

            // load sub directory.
            this._client.GetDirectoriesCompleted +=
                   new EventHandler<GetDirectoriesCompletedEventArgs>

                                           (this.GetDirectoriesCompleted);

            this._client.GetDirectoriesAsync(di.PhysicalPath);
        }
    }
}

private void GetDirectoriesCompleted(object sender, GetDirectoriesCompletedEventArgs e)
{
    if (e.Error != null)
        // error handler.

    else
    {
        foreach (ServerDirectoryBrowserService.DirectoryInfo di in e.Result)
        {
            TreeViewItem item = new TreeViewItem();
            item.Tag = di;
            item.Header = di.Name;
            (this.tvDirectory.SelectedItem as TreeViewItem).Items.Add(item);
        }
    }

    this.UpdateState("Ready.");
}

不過這段程式碼中隱含了一個陷阱,就是 event registration,目前這個版本會在每次要下載目錄時都被註冊一次事件常式,這會導致說你的程式要求幾次,這個事件常式就會被呼叫幾次的問題。

例如 Server 有兩個磁碟 C 和 D,則剛開始是:

C:\
D:\

後來當要展開 C:\ 時,會列出 C:\ 下的第一個子目錄(第一次呼叫):

C:\
   C:\1
   C:\2
D:\

但當要展開 D:\ 時,會發現事件被呼叫了兩次(第二次呼叫):

C:\
   C:\1
   C:\2
D:\
   D:\3
   D:\4
   D:\3
   D:\4

解決的方法是把事件註冊的程式拿到最前面一開始初始化時,這樣就不會有這個問題了:

void Page()
{
    this._client.GetDirectoriesCompleted += 
          new EventHandler<GetDirectoriesCompletedEventArgs>
          (this.GetDirectoriesCompleted);

}

 

// 由伺服器撈取目錄,透過 WCF Service 來做。
private void tvDirectory_SelectedItemChanged(object sender, RoutedEventArgs e)
{
    TreeViewItem item = this.tvDirectory.SelectedItem as TreeViewItem;

    if (!item.HasItems)
    {
        ServerDirectoryBrowserService.DirectoryInfo di =
            item.Tag as ServerDirectoryBrowserService.DirectoryInfo;

        if (di.HasSubdirectory)
        {
            this.UpdateState("Loading directory information...");

            // load sub directory.
           
this._client.GetDirectoriesAsync(di.PhysicalPath);
        }
    }
}

private void GetDirectoriesCompleted(object sender, GetDirectoriesCompletedEventArgs e)
{
    if (e.Error != null)
        // error handler.

    else
    {
        foreach (ServerDirectoryBrowserService.DirectoryInfo di in e.Result)
        {
            TreeViewItem item = new TreeViewItem();
            item.Tag = di;
            item.Header = di.Name;
            (this.tvDirectory.SelectedItem as TreeViewItem).Items.Add(item);
        }
    }

    this.UpdateState("Ready.");
}