ค้นหาการควบคุมทั้งหมดใน WPF Window ตามประเภท


218

ฉันกำลังมองหาวิธีที่จะหาการควบคุมทั้งหมดบนหน้าต่างตามประเภทของพวกเขา

ตัวอย่างเช่น:ค้นหาทั้งหมดTextBoxes, ค้นหาตัวควบคุมทั้งหมดที่ใช้อินเทอร์เฟซเฉพาะเป็นต้น


ในขณะที่เราอยู่ในหัวข้อนี้ก็เป็นสิ่งที่เกี่ยวข้องgoo.gl/i9RVx
Andrija

ฉันยังเขียนบล็อกโพสต์ในหัวข้อ: การปรับเปลี่ยน ControlTemplate ที่รันไทม์
Adolfo Perez

คำตอบ:


430

สิ่งนี้ควรทำเคล็ดลับ

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;
            }
        }
    }
}

จากนั้นคุณระบุการควบคุมเช่นนั้น

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

68
หมายเหตุ: หากคุณกำลังพยายามทำให้เรื่องนี้ทำงานและพบว่าหน้าต่างของคุณ (เช่น) มีเด็ก ๆ เป็นภาพ 0 คนให้ลองใช้วิธีการนี้ในตัวจัดการเหตุการณ์ที่โหลด หากคุณเรียกใช้งานใน Constructor (แม้หลังจาก InitializeComponent ()) เด็กที่มองเห็นจะยังไม่ได้โหลดและมันจะไม่ทำงาน
Ryan Lundy

24
การเปลี่ยนจาก VisualTreeHelper เป็น LogicalTreeHelpers จะทำให้องค์ประกอบที่มองไม่เห็นรวมอยู่ด้วย
Mathias Lykkegaard Lorenzen

11
บรรทัด "child! = null && child ไม่ใช่ T" ซ้ำซ้อนหรือไม่ ไม่ควรอ่านเพียงแค่ "child is T"
เที่ยง

1
ฉันจะเปลี่ยนเป็นวิธีการขยายโดยยืนยันthisก่อนDependencyObject=>this DependencyObject depObj
Johannes Wanzek

1
@JohannesWanzek อย่าลืมว่าคุณจะต้องเปลี่ยนบิตที่คุณเรียกเด็ก ๆ ด้วย: foreach (ChildofChild.FindVisualChildren <T> ()) {bla bla bla}
จะ

66

นี่เป็นวิธีที่ง่ายที่สุด:

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

โดยที่ control เป็นองค์ประกอบรูทของหน้าต่าง


1
คุณหมายถึงอะไร "องค์ประกอบราก" ฉันควรเขียนอะไรเพื่อเชื่อมต่อกับแบบฟอร์มหน้าต่างหลักของฉัน
เดดฟิช

ฉันได้รับมันใน XAML ดูผมต้องตั้งชื่อสำหรับตาราง<Grid Name="Anata_wa_yoru_o_shihai_suru_ai">here buttons</Grid>แล้วฉันสามารถใช้Anata_wa_yoru_o_shihai_suru_ai.Children.OfType<myType>();
deadfish

68
สิ่งนี้ไม่ตอบคำถามที่ถูกถาม ส่งคืนการควบคุมระดับลึกเพียงระดับเดียวเท่านั้น
Jim

21

ผมดัดแปลงคำตอบ @Bryce Kahle ที่จะปฏิบัติตามข้อเสนอแนะ @Mathias Lykkegaard Lorenzen LogicalTreeHelperและการใช้งาน

ดูเหมือนว่าจะทำงานได้ดี ;)

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;
                }
            }
        }
    }
}

(มันจะไม่ตรวจสอบการควบคุมแท็บหรือกริดภายใน GroupBox ตามที่กล่าวถึงโดย @Benjamin Berry & @David R ตามลำดับ) (ตามด้วยคำแนะนำของ @ noonand & ลบเด็กที่ซ้ำซ้อน! = null)


รับการมองหาในขณะที่วิธีการล้างกล่องข้อความทั้งหมดของฉันฉันมีหลายแท็บและนี่คือรหัสเดียวที่ทำงาน :) ขอบคุณ
JohnChris

13

ใช้คลาสตัวช่วยVisualTreeHelperหรือLogicalTreeHelperขึ้นอยู่กับต้นไม้ที่คุณสนใจทั้งคู่มีวิธีการรับลูกองค์ประกอบ (แม้ว่าไวยากรณ์จะแตกต่างกันเล็กน้อย) ฉันมักจะใช้คลาสเหล่านี้เพื่อค้นหาการเกิดขึ้นครั้งแรกของประเภทเฉพาะ แต่คุณสามารถปรับเปลี่ยนได้อย่างง่ายดายเพื่อค้นหาวัตถุประเภทนั้นทั้งหมด:

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 สำหรับคำอธิบายและการโพสต์ แต่ไบรซ์ Kahle โพสต์ฟังก์ชั่นที่ใช้งานได้อย่างเต็มที่ขอบคุณ
Andrija

สิ่งนี้ไม่ได้แก้ไขปัญหาของคำถามและคำตอบของประเภททั่วไปนั้นชัดเจนกว่ามาก การรวมเข้ากับการใช้ VisualTreeHelper.GetChildrenCount (obj) จะแก้ไขปัญหาได้ อย่างไรก็ตามมีประโยชน์ที่จะพิจารณาว่าเป็นตัวเลือก
Vasil Popov

9

ฉันพบว่าบรรทัดที่VisualTreeHelper.GetChildrenCount(depObj);ใช้ในตัวอย่างด้านบนไม่ได้ส่งกลับจำนวนที่ไม่เป็นศูนย์สำหรับGroupBoxes โดยเฉพาะอย่างยิ่งที่GroupBoxมีGridและและGridองค์ประกอบที่มีเด็ก ฉันเชื่อว่าอาจเป็นเพราะGroupBoxไม่ได้รับอนุญาตให้มีเด็กมากกว่าหนึ่งคนและสิ่งนี้ถูกเก็บไว้ในContentทรัพย์สินของมัน ไม่มีGroupBox.Childrenประเภทของทรัพย์สิน ฉันแน่ใจว่าฉันไม่ได้ทำอย่างมีประสิทธิภาพมาก แต่ฉันแก้ไขตัวอย่าง "FindVisualChildren" แรกในกลุ่มนี้ดังนี้:

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

ในการรับรายการประเภทลูกทั้งหมดคุณสามารถใช้:

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

เปลี่ยนการเรียกซ้ำเป็นเล็กน้อยเพื่อให้คุณสามารถค้นหาการควบคุมแท็บย่อยของการควบคุมแท็บได้

    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

นี่คืออีกเวอร์ชั่นกะทัดรัดที่มีไวยากรณ์ทั่วไป:

    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

และนี่คือวิธีการทำงาน

    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

โปรดทราบว่าการใช้ VisualTreeHelper ใช้งานได้เฉพาะกับส่วนควบคุมที่ได้รับจาก Visual หรือ Visual3D เท่านั้น หากคุณต้องการตรวจสอบองค์ประกอบอื่น ๆ (เช่น TextBlock, FlowDocument เป็นต้น) การใช้ VisualTreeHelper จะทำให้เกิดข้อยกเว้น

นี่เป็นอีกทางเลือกหนึ่งที่จะกลับไปที่แผนผังโลจิคัลหากจำเป็น

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


1

ฉันต้องการเพิ่มความคิดเห็น แต่ฉันมีน้อยกว่า 50 แต้มดังนั้นฉันจึงสามารถ "ตอบ" ได้เท่านั้น โปรดระวังว่าถ้าคุณใช้วิธี "VisualTreeHelper" เพื่อดึงวัตถุ XAML "TextBlock" จากนั้นก็จะดึงวัตถุ XAML "ปุ่ม" หากคุณเริ่มต้นวัตถุ "TextBlock" อีกครั้งโดยการเขียนพารามิเตอร์ Textblock.Text คุณจะไม่สามารถเปลี่ยนข้อความของปุ่มโดยใช้พารามิเตอร์ Button.Content ปุ่มจะแสดงข้อความที่ถูกเขียนอย่างถาวรจาก Textblock.Text write action (จากเมื่อถูกดึงมา -

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.
}

ในการแก้ไขปัญหานี้คุณสามารถลองใช้ XAML "กล่องข้อความ" และเพิ่มวิธีการ (หรือเหตุการณ์) เพื่อเลียนแบบปุ่ม XAMAL XAML "กล่องข้อความ" ไม่ได้ถูกรวบรวมโดยการค้นหา "TextBlock"


นั่นคือความแตกต่างระหว่างภาพและต้นไม้ตรรกะ แผนผังต้นไม้มีการควบคุมทุกอย่าง (รวมถึงการควบคุมที่สร้างขึ้นซึ่งกำหนดไว้ในเทมเพลตการควบคุม) ในขณะที่โลจิคัลทรีมีการควบคุมจริงเท่านั้น มีการสร้างภาพที่ดีของแนวคิดนี้ที่นี่: ลิงค์
lauxjpn

1

รุ่นของฉันสำหรับ 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

ด้วยเหตุผลบางอย่างไม่มีคำตอบที่โพสต์ที่นี่ช่วยให้ฉันได้รับการควบคุมทั้งหมดของประเภทที่กำหนดอยู่ในการควบคุมที่กำหนดใน MainWindow ของฉัน ฉันต้องการค้นหารายการเมนูทั้งหมดในเมนูเดียวเพื่อทำซ้ำ พวกเขาไม่ใช่ทายาทโดยตรงของเมนูดังนั้นฉันจัดการเพื่อรวบรวมเฉพาะ lilne แรกของพวกเขาโดยใช้รหัสใด ๆ ข้างต้น วิธีการขยายนี้เป็นวิธีการแก้ปัญหาของฉันสำหรับทุกคนที่จะอ่านต่อไปจนถึงที่นี่

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);
                }
            }
        }
    }

หวังว่ามันจะช่วย


1

คำตอบที่ได้รับการยอมรับผลตอบแทนองค์ประกอบพบว่ามากหรือน้อยไม่เรียงลำดับโดยต่อไปนี้สาขาลูกคนแรกให้ลึกที่สุดเท่าที่เป็นไปได้ในขณะที่ผลผลิตองค์ประกอบที่ค้นพบไปพร้อมกันก่อนที่จะย้อนรอยและการทำซ้ำขั้นตอนสำหรับการที่ยังไม่ได้แยกวิเคราะห์กิ่งไม้

หากคุณต้องการองค์ประกอบเรียงลำดับจากมากไปน้อยซึ่งจะให้ผลโดยตรงกับลูกก่อนจากนั้นลูกของพวกเขาและอื่น ๆ อัลกอริทึมต่อไปนี้จะทำงาน:

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;
        }
    }
}

องค์ประกอบที่เป็นผลลัพธ์จะถูกจัดเรียงจากใกล้ที่สุดไปหาไกลที่สุด สิ่งนี้จะเป็นประโยชน์เช่นหากคุณกำลังมองหาองค์ประกอบย่อยที่ใกล้ที่สุดของบางประเภทและเงื่อนไข:

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

1
มีบางอย่างขาดหายไป; childไม่ได้กำหนด
codebender

1

@ ไบรซ์คำตอบที่ดีจริงๆ

รุ่น 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

การใช้งาน (สิ่งนี้ปิดการใช้งานกล่องข้อความทั้งหมดในหน้าต่าง):

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

-1

ฉันพบว่าง่ายขึ้นโดยไม่มี Visual Tree Helpers:

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

3
สิ่งนี้ลึกเพียงระดับเดียวเท่านั้น ใน XAML คุณมีการควบคุมหลายระดับ
ตำรวจ SQL
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.