Temukan semua kontrol di WPF Window berdasarkan jenis

218

Saya mencari cara untuk menemukan semua kontrol di Window berdasarkan tipenya,

misalnya: temukan semua TextBoxes, temukan semua kontrol yang mengimplementasikan antarmuka spesifik, dll.

Andrija
sumber
sementara kita membahas topik ini, ini juga relevan goo.gl/i9RVx
Andrija
Saya juga menulis posting blog dengan topik: Memodifikasi ControlTemplate di Runtime
Adolfo Perez

Jawaban:

430

Ini harus melakukan trik

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

maka Anda menghitung kontrol seperti itu

foreach (TextBlock tb in FindVisualChildren<TextBlock>(window))
{
    // do something with tb here
}
Bryce Kahle
sumber
68
Catatan: Jika Anda mencoba membuatnya berfungsi dan menemukan bahwa Jendela Anda (misalnya) memiliki 0 anak visual, coba jalankan metode ini di pengendali event Loaded. Jika Anda menjalankannya di konstruktor (bahkan setelah InitializeComponent ()), anak-anak visual belum dimuat, dan itu tidak akan berfungsi.
Ryan Lundy
24
Beralih dari VisualTreeHelper ke LogicalTreeHelpers akan menyebabkan elemen yang tidak terlihat dimasukkan juga.
Mathias Lykkegaard Lorenzen
11
Bukankah kalimat "child! = Null && child is T" berlebihan? Seharusnya tidak hanya membaca "child is T"
siang hari dan
1
Saya akan mengubahnya menjadi metode perpanjangan dengan hanya memasukkan thissebelum DependencyObject=>this DependencyObject depObj
Johannes Wanzek
1
@JohannesWanzek Jangan lupa Anda juga perlu mengubah bit tempat Anda menyebutnya pada anak: foreach (ChildofChild.FindVisualChildren <T> ()) {bla bla bla}
Will
66

Ini cara termudah:

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

di mana kontrol adalah elemen root dari jendela.

Joel
sumber
1
apa maksudmu "elemen root"? Apa yang harus saya tulis agar terhubung dengan formulir jendela utama saya?
deadfish
Saya mengerti, dalam tampilan xaml saya harus menetapkan nama untuk grid <Grid Name="Anata_wa_yoru_o_shihai_suru_ai">here buttons</Grid>dan kemudian saya bisa menggunakanAnata_wa_yoru_o_shihai_suru_ai.Children.OfType<myType>();
deadfish
68
Ini tidak menjawab pertanyaan yang diajukan. Ini hanya mengembalikan kontrol anak sedalam satu level.
Jim
21

Saya mengadaptasi jawaban @Bryce Kahle untuk mengikuti saran dan penggunaan @Mathias Lykkegaard Lorenzen LogicalTreeHelper .

Tampaknya bekerja dengan baik. ;)

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

(Masih tidak akan memeriksa kontrol tab atau Grid di dalam GroupBoxes seperti yang disebutkan oleh @Benjamin Berry & @David R masing-masing.) (Juga mengikuti saran @ noonand & menghapus anak yang berlebihan! = Null)

Simon F
sumber
telah mencari beberapa saat bagaimana menghapus semua kotak teks saya, saya memiliki banyak tab dan ini adalah satu-satunya kode yang bekerja :) terima kasih
JohnChris
13

Gunakan kelas helper VisualTreeHelperatau LogicalTreeHelpertergantung pada pohon yang Anda minati. Keduanya menyediakan metode untuk mendapatkan anak-anak elemen (meskipun sintaks sedikit berbeda). Saya sering menggunakan kelas-kelas ini untuk menemukan kemunculan pertama dari jenis tertentu, tetapi Anda dapat dengan mudah memodifikasinya untuk menemukan semua objek jenis itu:

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;
}
Oskar
sumber
+1 untuk penjelasan dan posting tetapi Bryce Kahle memposting fungsi yang sepenuhnya berfungsi Terima kasih
Andrija
Ini tidak memperbaiki masalah pertanyaan, dan juga jawaban dengan tipe generik jauh lebih jelas. Menggabungkannya dengan penggunaan VisualTreeHelper.GetChildrenCount (obj) akan memperbaiki masalah ini. Namun bermanfaat untuk dipertimbangkan sebagai opsi.
Vasil Popov
9

Saya menemukan bahwa garis,, yang VisualTreeHelper.GetChildrenCount(depObj);digunakan dalam beberapa contoh di atas tidak mengembalikan jumlah bukan nol untuk GroupBoxes, khususnya, di mana GroupBoxmengandung a Grid, dan Gridmengandung elemen anak-anak. Saya percaya ini mungkin karena GroupBoxtidak boleh mengandung lebih dari satu anak, dan ini disimpan di Contentpropertinya. Tidak ada GroupBox.Childrentipe properti. Saya yakin saya tidak melakukan ini dengan sangat efisien, tetapi saya memodifikasi contoh "FindVisualChildren" pertama dalam rantai ini sebagai berikut:

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; 
            } 
        }
    }
} 
David R
sumber
4

Untuk mendapatkan daftar semua anak dari jenis tertentu, Anda dapat menggunakan:

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;
}
Michael
sumber
4

Perubahan kecil untuk rekursi sehingga Anda dapat misalnya menemukan kontrol tab anak dari kontrol 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;
    }
Benjamin Berry
sumber
3

Berikut ini adalah versi ringkas lainnya, dengan sintaksis generik:

    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;
        }
    }
pengguna1656671
sumber
2

Dan ini cara kerjanya ke atas

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

sumber
2

Perhatikan bahwa menggunakan VisualTreeHelper hanya berfungsi pada kontrol yang berasal dari Visual atau Visual3D. Jika Anda juga perlu memeriksa elemen-elemen lain (misalnya TextBlock, FlowDocument dll.), Menggunakan VisualTreeHelper akan memberikan pengecualian.

Berikut adalah alternatif yang jatuh kembali ke pohon logis jika perlu:

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

Philipp
sumber
1

Saya ingin menambahkan komentar tetapi saya memiliki kurang dari 50 poin sehingga saya hanya dapat "Jawab". Ketahuilah bahwa jika Anda menggunakan metode "VisualTreeHelper" untuk mengambil objek XAML "TextBlock" maka ia juga akan mengambil objek "Tombol" XAML. Jika Anda menginisialisasi ulang objek "TextBlock" dengan menulis ke parameter Textblock.Text maka Anda tidak lagi dapat mengubah teks Button menggunakan parameter Button.Content. Tombol akan secara permanen menampilkan teks yang ditulis kepadanya dari Textblock.Teks tulis tindakan (dari saat itu diambil -

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

Untuk mengatasinya, Anda dapat mencoba menggunakan XAML "TextBox" dan menambahkan metode (atau Acara) untuk meniru Tombol XAMAL. XAML "TextBox" tidak dikumpulkan oleh pencarian untuk "TextBlock".

Lifygen
sumber
Itulah perbedaan antara pohon visual dan logis. Pohon visual berisi setiap kontrol (termasuk kontrol yang dibuat, yang didefinisikan dalam templat kontrol) sementara pohon logis hanya berisi kontrol aktual (tanpa yang ditentukan dalam templat). Ada visualisasi yang bagus dari konsep ini di sini: tautan
lauxjpn
1

Versi saya untuk 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;
    };
Whiso
sumber
1

Untuk beberapa alasan, tidak ada jawaban yang diposting di sini membantu saya untuk mendapatkan semua kontrol dari tipe yang diberikan terkandung dalam kontrol yang diberikan di MainWindow saya. Saya perlu menemukan semua item menu dalam satu menu untuk mengulanginya. Mereka tidak semua keturunan langsung dari menu, jadi saya berhasil mengumpulkan hanya lilne pertama dari mereka menggunakan salah satu kode di atas. Metode ekstensi ini adalah solusi saya untuk masalah bagi siapa saja yang akan terus membaca sampai sini.

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

Semoga ini bisa membantu.

αNerd
sumber
1

The jawaban yang diterima mengembalikan unsur-unsur yang ditemukan kurang lebih unordered , dengan mengikuti cabang anak pertama sedalam mungkin, sementara menghasilkan unsur-unsur ditemukan di sepanjang jalan, sebelum backtracking dan mengulangi langkah-langkah untuk cabang-cabang pohon belum diurai.

Jika Anda membutuhkan elemen turunan dalam urutan menurun , di mana anak-anak langsung akan dihasilkan pertama, maka anak-anak mereka dan seterusnya, algoritma berikut akan bekerja:

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

Elemen yang dihasilkan akan dipesan dari yang terdekat hingga yang terjauh. Ini akan berguna misalnya jika Anda mencari elemen anak terdekat dari beberapa jenis dan kondisi:

var foundElement = GetDescendants<StackPanel>(someElement)
                       .FirstOrDefault(o => o.SomeProperty == SomeState);
lauxjpn
sumber
1
Apakah ada yang hilang; childtidak terdefinisi.
codebender
1

@ Bryce, jawaban yang sangat bagus.

Versi 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

Penggunaan (ini menonaktifkan semua Kotak Teks di jendela):

        For Each tb As TextBox In FindVisualChildren(Of TextBox)(Me)
          tb.IsEnabled = False
        Next
Andrea Antonangeli
sumber
-1

Saya merasa lebih mudah tanpa Pembantu Pohon Visual:

foreach (UIElement element in MainWindow.Children) {
    if (element is TextBox) { 
        if ((element as TextBox).Text != "")
        {
            //Do something
        }
    }
};
Rafael Ventura
sumber
3
Ini hanya kedalaman satu tingkat. di XAML Anda memiliki kontrol yang sangat bersarang.
SQL Police