(WPF) 在 WPF 視窗中找到所有控制項

在 WPF 視窗裡, 控制項模型和 Windows Form 或 Web Form 之間都有或大或小的差異。就以巡覽控制項這件事來說吧, 你可能會和我一樣, 對於容器(包括視窗本身)都沒有提供 Controls 屬性可用而深感不方便。它甚至也不提供像 XML 和 LINQ to XML 之下的各種巡覽功能, 像是 Descendants 屬性...

在 WPF 視窗裡, 控制項模型和 Windows Form 或 Web Form 之間都有或大或小的差異。就以巡覽控制項這件事來說吧, 你可能會和我一樣, 對於容器(包括視窗本身)都沒有提供 Controls 屬性可用而深感不方便。它甚至也不提供像 XML 和 LINQ to XML 之下的各種巡覽功能, 像是 Descendants 屬性。

我最近打算在我的 WPF 程式中把某個 StackPanel 裡面的所有 TextBox 一次 Disable 或 Enable。在我最熟悉的 ASP.NET 中, 這是很簡單的一件事。如果是客戶端程式的話, 我甚至可以使用 jQuery 來輕鬆的辦到。很可惜, 在 WPF 程式中., 似乎並沒有提供現成的類似功能。

透過 Panel.Children 進行巡覽

山不轉人轉。首先, 我發現像 StackPanel 或 WrapPanel 這類容器都有 Children 屬性, 可以將底下的所有子控制項條列為 UIElement 類別的物件。UIElement 是 WPF 中獨有的基底類別 (Control 和 Panel 均繼承自 FrameworkElement, 而 FrameworkElement 繼承自 UIElement)。其用法如下:


foreach (UIElement child in WrapPanel1.Children)
{
   ...
}

很可惜, 雖然我們可以使用這個方法條列出容器之下的各控制項, 我們只能得到第一層(如同 XML 中的 Nodes)。如果這個容器之內還有容器, 我們就得想想其它辦法。

幸好, 我發現 WPF 中各種容器, 包括 WrapPanel, StackPanel 等等, 都繼承自 Panel 這個基底類別。所以只要簡的轉型, 就可以使用遞迴的做法巡覽及條列各種不同容器裡面的所有控制項, 而不會只限於第一層了。

以下, 假設我要把某個 WrapPanel 裡的所有 TextBox 控制項一次全部 Disable 或 Enable, 方法可以這樣寫:


void disableAllTextBoxes(object control)
{
    if (control is TextBox)
        ((TextBox)control).IsEnabled = false;
    else
        if (control is Panel)
            foreach (UIElement child in ((Panel)control).Children)
                disableAllTextBoxes(child);
}

void enableAllTextBoxes(object control)
{
    if (control is TextBox)
        ((TextBox)control).IsEnabled = true;
    else
        if (control is Panel)
            foreach (UIElement child in ((Panel)control).Children)
                enableAllTextBoxes(child);
}

如果你的 WrapPanel 的 Name 是 wpEdit  的話, 以下這道指令即可把其它所有 TextBox 全部變成 disabled:


disableAllTextBoxes(wpEdit);

你也可以把 WPF 最外層的那個 Grid 當作參數(當然, 你得先給它取個名字), 然後你就可以針對整個視窗裡的物件動作了。

透過 LogicalTreeHelper 進行巡覽

@2012/3/1 感謝程湘之間的補充。他提到我們可以透過 LogicalTreeHelper 這個靜態類別取得 WPF 控制項的子控制項。

毋庸置疑的, LogicalTreeHelper 既然號稱是 Helper, 它自然可以幫我們帶來一些方便。最特別的是它可以運用在各種控制項上面, 而不僅侷限於 Panel。以下, 我把上面的程式修改成運用 LogicalTreeHelper 的功能:


void disableAllTextBoxesAlternative(object control)
{
    if (!(control is DependencyObject)) return;
    if (control is TextBox)
        ((TextBox)control).IsEnabled = false;
    else
        foreach (object child in LogicalTreeHelper.GetChildren(control as DependencyObject))
            disableAllTextBoxesAlternative(child);
}

void enableAllTextBoxesAlternative(object control)
{
    if (!(control is DependencyObject)) return;
    if (control is TextBox)
        ((TextBox)control).IsEnabled = true;
    else
        foreach (object child in LogicalTreeHelper.GetChildren(control as DependencyObject))
            enableAllTextBoxesAlternative(child);
}

在這個程式中, 我們只需要把 this (即主視窗) 當作參數丟進去就可以了, 不需要特別選擇某個容器物件: 


disableAllTextBoxesAlternative(this); 

除了 LogicalTreeHelper, WPF 另外還提供了 VisualTreeHelper 類別 (請注意它與 LogicalTreeHelper 位於不同的命名空間之下), 功能有點類似, 但是可以巡覽更詳細的各種子控制項, 例如按鈕的 Border, 下拉式選單的 ScrollBar 等等。這對一般程式設計師而言, 可能比較用不到 (除非你用到了 Control Template 功能); 所以就此帶過。

很可惜的, 就本文章的情境而言, LogicalTreeHelper 並沒有為我們帶來額外的好處, 因為我們仍然必須使用遞迴的做法, 才能一一巡覽子控制項中的子控制項。此外, 最要命的, 是 LogicalTreeHelper 在 SilverLight 中並沒有提供。如果你寫 SilverLight 程式, 恐怕你只能使用最上面的那種做法。

參考資料:

 


Dev 2Share @ 點部落