Tìm tất cả các điều khiển trong WPF Window theo loại


218

Tôi đang tìm cách để tìm tất cả các điều khiển trên Window theo loại của chúng,

ví dụ: tìm tất cả TextBoxes, tìm tất cả các điều khiển thực hiện giao diện cụ thể, v.v.


trong khi chúng ta đang ở trong chủ đề này, điều này cũng có liên quan goo.gl/i9RVx
Andrija

Tôi cũng đã viết một bài đăng trên blog về chủ đề: Sửa đổi ControlTemplate trong thời gian chạy
Adolfo Perez

Câu trả lời:


430

cái này cần phải dùng mẹo

public static IEnumerable<T> FindVisualChildren<T>(DependencyObject depObj) where T : DependencyObject
{
    if (depObj != null)
    {
        for (int i = 0; i < VisualTreeHelper.GetChildrenCount(depObj); i++)
        {
            DependencyObject child = VisualTreeHelper.GetChild(depObj, i);
            if (child != null && child is T)
            {
                yield return (T)child;
            }

            foreach (T childOfChild in FindVisualChildren<T>(child))
            {
                yield return childOfChild;
            }
        }
    }
}

sau đó bạn liệt kê các điều khiển như vậy

foreach (TextBlock tb in FindVisualChildren<TextBlock>(window))
{
    // do something with tb here
}

68
Lưu ý: Nếu bạn đang cố gắng để điều này hoạt động và thấy rằng Cửa sổ của bạn (ví dụ) có 0 con trực quan, hãy thử chạy phương thức này trong trình xử lý sự kiện đã tải. Nếu bạn chạy nó trong hàm tạo (ngay cả sau khi LaunchizeComponent ()), các hình ảnh con chưa được tải và nó sẽ không hoạt động.
Ryan Lundy

24
Chuyển đổi từ VisualTreeHelper sang LogicalTreeHelpers cũng sẽ khiến các yếu tố vô hình được đưa vào.
Mathias Lykkegaard Lorenzen

11
Không phải là dòng "con! = Null && con là T" dư thừa sao? Không nên đọc "trẻ em là T"
buổi trưa

1
Tôi sẽ biến nó thành một phương thức mở rộng chỉ bằng cách đánh dấu thistrước DependencyObject=>this DependencyObject depObj
Julian Wanzek

1
@JohannesWanzek Đừng quên bạn cũng cần thay đổi bit nơi bạn gọi nó trên đứa trẻ: foreach (ChildofChild.FindVisualChildren <T> ()) {bla bla bla}
Sẽ

66

Đây là cách dễ dàng nhất:

IEnumerable<myType> collection = control.Children.OfType<myType>(); 

trong đó điều khiển là phần tử gốc của cửa sổ.


1
"phần tử gốc" nghĩa là gì? Tôi nên viết gì để kết nối với biểu mẫu chính của mình?
cá chết

Tôi hiểu rồi, trong chế độ xem xaml tôi phải đặt tên cho lưới <Grid Name="Anata_wa_yoru_o_shihai_suru_ai">here buttons</Grid>và sau đó tôi có thể sử dụngAnata_wa_yoru_o_shihai_suru_ai.Children.OfType<myType>();
cá chết

68
Điều này không trả lời câu hỏi đã được hỏi. Nó chỉ trả về điều khiển con sâu một cấp.
Jim

21

Tôi đã điều chỉnh câu trả lời của @Bryce Kahle để làm theo gợi ý và cách sử dụng của @Mathias Lykkegaard Lorenzen LogicalTreeHelper.

Có vẻ làm việc ổn. ;)

public static IEnumerable<T> FindLogicalChildren<T> ( DependencyObject depObj ) where T : DependencyObject
{
    if( depObj != null )
    {
        foreach( object rawChild in LogicalTreeHelper.GetChildren( depObj ) )
        {
            if( rawChild is DependencyObject )
            {
                DependencyObject child = (DependencyObject)rawChild;
                if( child is T )
                {
                    yield return (T)child;
                }

                foreach( T childOfChild in FindLogicalChildren<T>( child ) ) 
                {
                    yield return childOfChild;
                }
            }
        }
    }
}

(Nó vẫn sẽ không kiểm tra các điều khiển tab hoặc Lưới trong GroupBox như được đề cập bởi @Benjamin Berry & @David R tương ứng.)


Tìm kiếm một lúc làm thế nào để xóa tất cả các hộp văn bản của tôi, tôi có nhiều tab và đây là mã duy nhất hoạt động :) cảm ơn
JohnChris

13

Sử dụng các lớp của trình trợ giúp VisualTreeHelperhoặc LogicalTreeHelpertùy thuộc vào loại cây bạn quan tâm. Cả hai đều cung cấp các phương thức để lấy phần tử con của một phần tử (mặc dù cú pháp khác nhau một chút). Tôi thường sử dụng các lớp này để tìm sự xuất hiện đầu tiên của một loại cụ thể, nhưng bạn có thể dễ dàng sửa đổi nó để tìm tất cả các đối tượng của loại đó:

public static DependencyObject FindInVisualTreeDown(DependencyObject obj, Type type)
{
    if (obj != null)
    {
        if (obj.GetType() == type)
        {
            return obj;
        }

        for (int i = 0; i < VisualTreeHelper.GetChildrenCount(obj); i++)
        {
            DependencyObject childReturn = FindInVisualTreeDown(VisualTreeHelper.GetChild(obj, i), type);
            if (childReturn != null)
            {
                return childReturn;
            }
        }
    }

    return null;
}

+1 để giải thích và đăng bài nhưng Bryce Kahle đã đăng chức năng hoàn toàn hoạt động Cảm ơn
Andrija

Điều này không khắc phục vấn đề của câu hỏi, và câu trả lời với loại chung chung rõ ràng hơn nhiều. Kết hợp nó với việc sử dụng VisualTreeHelper.GetChildrenCount (obj) sẽ khắc phục vấn đề. Tuy nhiên là hữu ích để được coi là một lựa chọn.
Vasil Popov

9

Tôi thấy rằng dòng ,, VisualTreeHelper.GetChildrenCount(depObj);được sử dụng trong một số ví dụ ở trên không trả về số khác không cho GroupBoxes, đặc biệt, trong đó GroupBoxchứa a GridGridchứa các phần tử con. Tôi tin rằng điều này có thể là do GroupBoxkhông được phép chứa nhiều hơn một đứa trẻ và điều này được lưu trữ trong Contenttài sản của nó . Không có GroupBox.Childrenloại tài sản. Tôi chắc chắn rằng tôi đã không làm điều này rất hiệu quả, nhưng tôi đã sửa đổi ví dụ "FindVisualChildren" đầu tiên trong chuỗi này như sau:

public IEnumerable<T> FindVisualChildren<T>(DependencyObject depObj) where T : DependencyObject 
{ 
    if (depObj != null) 
    {
        int depObjCount = VisualTreeHelper.GetChildrenCount(depObj); 
        for (int i = 0; i <depObjCount; i++) 
        { 
            DependencyObject child = VisualTreeHelper.GetChild(depObj, i); 
            if (child != null && child is T) 
            { 
                yield return (T)child; 
            }

            if (child is GroupBox)
            {
                GroupBox gb = child as GroupBox;
                Object gpchild = gb.Content;
                if (gpchild is T)
                {
                    yield return (T)child; 
                    child = gpchild as T;
                }
            }

            foreach (T childOfChild in FindVisualChildren<T>(child)) 
            { 
                yield return childOfChild; 
            } 
        }
    }
} 

4

Để có danh sách tất cả trẻ em thuộc một loại cụ thể, bạn có thể sử dụng:

private static IEnumerable<DependencyObject> FindInVisualTreeDown(DependencyObject obj, Type type)
{
    if (obj != null)
    {
        if (obj.GetType() == type)
        {
            yield return obj;
        }

        for (var i = 0; i < VisualTreeHelper.GetChildrenCount(obj); i++)
        {
            foreach (var child in FindInVisualTreeDown(VisualTreeHelper.GetChild(obj, i), type))
            {
                if (child != null)
                {
                    yield return child;
                }
            }
        }
    }

    yield break;
}

4

Thay đổi nhỏ đối với đệ quy để bạn có thể tìm ví dụ điều khiển tab con của điều khiển tab.

    public static DependencyObject FindInVisualTreeDown(DependencyObject obj, Type type)
    {
        if (obj != null)
        {
            for (int i = 0; i < VisualTreeHelper.GetChildrenCount(obj); i++)
            {
                DependencyObject child = VisualTreeHelper.GetChild(obj, i);

                if (child.GetType() == type)
                {
                    return child;
                }

                DependencyObject childReturn = FindInVisualTreeDown(child, type);
                if (childReturn != null)
                {
                    return childReturn;
                }
            }
        }

        return null;
    }

3

Đây là một phiên bản nhỏ gọn khác, với cú pháp chung:

    public static IEnumerable<T> FindLogicalChildren<T>(DependencyObject obj) where T : DependencyObject
    {
        if (obj != null) {
            if (obj is T)
                yield return obj as T;

            foreach (DependencyObject child in LogicalTreeHelper.GetChildren(obj).OfType<DependencyObject>()) 
                foreach (T c in FindLogicalChildren<T>(child)) 
                    yield return c;
        }
    }

2

Và đây là cách nó hoạt động trở lên

    private T FindParent<T>(DependencyObject item, Type StopAt) where T : class
    {
        if (item is T)
        {
            return item as T;
        }
        else
        {
            DependencyObject _parent = VisualTreeHelper.GetParent(item);
            if (_parent == null)
            {
                return default(T);
            }
            else
            {
                Type _type = _parent.GetType();
                if (StopAt != null)
                {
                    if ((_type.IsSubclassOf(StopAt) == true) || (_type == StopAt))
                    {
                        return null;
                    }
                }

                if ((_type.IsSubclassOf(typeof(T)) == true) || (_type == typeof(T)))
                {
                    return _parent as T;
                }
                else
                {
                    return FindParent<T>(_parent, StopAt);
                }
            }
        }
    }

2

Xin lưu ý rằng việc sử dụng VisualTreeHelper chỉ hoạt động trên các điều khiển xuất phát từ Visual hoặc Visual3D. Nếu bạn cũng cần kiểm tra các yếu tố khác (ví dụ TextBlock, FlowDocument, v.v.), sử dụng VisualTreeHelper sẽ đưa ra một ngoại lệ.

Đây là một thay thế quay trở lại cây logic nếu cần thiết:

http://www.hardcodet.net/2009/06/finding-elements-in-wpf-tree-both-ways


1

Tôi muốn thêm một bình luận nhưng tôi có ít hơn 50 điểm nên tôi chỉ có thể "Trả lời". Xin lưu ý rằng nếu bạn sử dụng phương thức "VisualTreeHelper" để truy xuất các đối tượng "TextBlock" của XAML thì nó cũng sẽ lấy các đối tượng "Nút" XAML. Nếu bạn khởi tạo lại đối tượng "TextBlock" bằng cách ghi vào tham số Textblock.Text thì bạn sẽ không còn có thể thay đổi văn bản Nút bằng tham số Button.Content. Nút sẽ hiển thị vĩnh viễn văn bản được ghi từ nó trong hành động ghi Textblock.Text (từ khi được truy xuất -

foreach (TextBlock tb in FindVisualChildren<TextBlock>(window))
{
// do something with tb here
   tb.Text = ""; //this will overwrite Button.Content and render the 
                 //Button.Content{set} permanently disabled.
}

Để khắc phục điều này, bạn có thể thử sử dụng "TextBox" XAML và thêm các phương thức (hoặc Sự kiện) để bắt chước Nút XAMAL. XAML "TextBox" không được thu thập bởi một tìm kiếm cho "TextBlock".


Đó là sự khác biệt giữa cây trực quan và cây logic. Cây trực quan chứa mọi điều khiển (bao gồm cả điều khiển được tạo, điều khiển được xác định trong mẫu điều khiển) trong khi cây logic chỉ chứa các điều khiển thực tế (không có các điều khiển được xác định trong mẫu). Có một hình dung đẹp về khái niệm này ở đây: link
lauxjpn

1

Phiên bản của tôi cho C ++ / CLI

template < class T, class U >
bool Isinst(U u) 
{
    return dynamic_cast< T >(u) != nullptr;
}

template <typename T>
    T FindVisualChildByType(Windows::UI::Xaml::DependencyObject^ element, Platform::String^ name)
    {
        if (Isinst<T>(element) && dynamic_cast<Windows::UI::Xaml::FrameworkElement^>(element)->Name == name)
        {
            return dynamic_cast<T>(element);
        }
        int childcount = Windows::UI::Xaml::Media::VisualTreeHelper::GetChildrenCount(element);
        for (int i = 0; i < childcount; ++i)
        {
            auto childElement = FindVisualChildByType<T>(Windows::UI::Xaml::Media::VisualTreeHelper::GetChild(element, i), name);
            if (childElement != nullptr)
            {
                return childElement;
            }
        }
        return nullptr;
    };

1

Vì một số lý do, không có câu trả lời nào được đăng ở đây giúp tôi có được tất cả các điều khiển thuộc loại đã cho có trong một điều khiển nhất định trong MainWindow của tôi. Tôi cần tìm tất cả các mục trong một menu để lặp lại chúng. Họ không phải là hậu duệ trực tiếp của thực đơn, vì vậy tôi chỉ thu thập được những lilne đầu tiên trong số họ bằng cách sử dụng bất kỳ mã nào ở trên. Phương pháp mở rộng này là giải pháp của tôi cho vấn đề cho bất kỳ ai sẽ tiếp tục đọc tất cả các cách ở đây.

public static void FindVisualChildren<T>(this ICollection<T> children, DependencyObject depObj) where T : DependencyObject
    {
        if (depObj != null)
        {
            var brethren = LogicalTreeHelper.GetChildren(depObj);
            var brethrenOfType = LogicalTreeHelper.GetChildren(depObj).OfType<T>();
            foreach (var childOfType in brethrenOfType)
            {
                children.Add(childOfType);
            }

            foreach (var rawChild in brethren)
            {
                if (rawChild is DependencyObject)
                {
                    var child = rawChild as DependencyObject;
                    FindVisualChildren<T>(children, child);
                }
            }
        }
    }

Hy vọng nó giúp.


1

Các câu trả lời được chấp nhận trở lại các yếu tố phát hiện nhiều hay ít không có thứ tự , bằng cách làm theo các chi nhánh đứa con đầu lòng như sâu càng tốt, trong khi năng suất các yếu tố phát hiện trên đường đi, trước khi thụt lùi và lặp lại các bước cho cành cây chưa phân tích cú pháp.

Nếu bạn cần các phần tử con theo thứ tự giảm dần , trong đó các con trực tiếp sẽ được sinh ra trước, sau đó con của chúng, v.v., thuật toán sau sẽ hoạt động:

public static IEnumerable<T> GetVisualDescendants<T>(DependencyObject parent, bool applyTemplates = false)
    where T : DependencyObject
{
    if (parent == null || !(child is Visual || child is Visual3D))
        yield break;

    var descendants = new Queue<DependencyObject>();
    descendants.Enqueue(parent);

    while (descendants.Count > 0)
    {
        var currentDescendant = descendants.Dequeue();

        if (applyTemplates)
            (currentDescendant as FrameworkElement)?.ApplyTemplate();

        for (var i = 0; i < VisualTreeHelper.GetChildrenCount(currentDescendant); i++)
        {
            var child = VisualTreeHelper.GetChild(currentDescendant, i);

            if (child is Visual || child is Visual3D)
                descendants.Enqueue(child);

            if (child is T foundObject)
                yield return foundObject;
        }
    }
}

Các yếu tố kết quả sẽ được sắp xếp từ gần nhất đến xa nhất. Điều này sẽ hữu ích, ví dụ nếu bạn đang tìm kiếm phần tử con gần nhất của một số loại và điều kiện:

var foundElement = GetDescendants<StackPanel>(someElement)
                       .FirstOrDefault(o => o.SomeProperty == SomeState);

1
Là một cái gì đó còn thiếu; childkhông định nghĩa được.
viết mã

1

@Bryce, câu trả lời thực sự tốt đẹp.

Phiên bản VB.NET:

Public Shared Iterator Function FindVisualChildren(Of T As DependencyObject)(depObj As DependencyObject) As IEnumerable(Of T)
    If depObj IsNot Nothing Then
        For i As Integer = 0 To VisualTreeHelper.GetChildrenCount(depObj) - 1
            Dim child As DependencyObject = VisualTreeHelper.GetChild(depObj, i)
            If child IsNot Nothing AndAlso TypeOf child Is T Then
                Yield DirectCast(child, T)
            End If
            For Each childOfChild As T In FindVisualChildren(Of T)(child)
                Yield childOfChild
            Next
        Next
    End If
End Function

Cách sử dụng (điều này vô hiệu hóa tất cả các TextBox trong một cửa sổ):

        For Each tb As TextBox In FindVisualChildren(Of TextBox)(Me)
          tb.IsEnabled = False
        Next

-1

Tôi thấy nó dễ dàng hơn mà không cần Visual Tree Helpers:

foreach (UIElement element in MainWindow.Children) {
    if (element is TextBox) { 
        if ((element as TextBox).Text != "")
        {
            //Do something
        }
    }
};

3
Điều này chỉ đi sâu một cấp. trong XAML bạn có các điều khiển lồng nhau sâu sắc.
Cảnh sát SQL
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.