Bagaimana saya bisa menemukan kontrol WPF berdasarkan nama atau tipe?

264

Saya perlu mencari hierarki kontrol WPF untuk kontrol yang cocok dengan nama atau tipe yang diberikan. Bagaimana saya bisa melakukan ini?

alex2k8
sumber

Jawaban:

311

Saya menggabungkan format template yang digunakan oleh John Myczek dan algoritma Tri Q di atas untuk membuat Algoritma findChild yang dapat digunakan pada orangtua mana pun. Perlu diingat bahwa pencarian pohon secara rekursif ke bawah bisa menjadi proses yang panjang. Saya hanya memeriksa ini di aplikasi WPF, beri komentar tentang kesalahan yang mungkin Anda temukan dan saya akan memperbaiki kode saya.

WPF Snoop adalah alat yang berguna dalam melihat pohon visual - Saya sangat merekomendasikan menggunakannya saat menguji atau menggunakan algoritma ini untuk memeriksa pekerjaan Anda.

Ada kesalahan kecil dalam Algoritma Tri Q. Setelah anak ditemukan, jika childrenCount> 1 dan kita mengulangi lagi kita dapat menimpa anak yang ditemukan dengan benar. Karena itu saya menambahkan if (foundChild != null) break;kode saya untuk menangani kondisi ini.

/// <summary>
/// Finds a Child of a given item in the visual tree. 
/// </summary>
/// <param name="parent">A direct parent of the queried item.</param>
/// <typeparam name="T">The type of the queried item.</typeparam>
/// <param name="childName">x:Name or Name of child. </param>
/// <returns>The first parent item that matches the submitted type parameter. 
/// If not matching item can be found, 
/// a null parent is being returned.</returns>
public static T FindChild<T>(DependencyObject parent, string childName)
   where T : DependencyObject
{    
  // Confirm parent and childName are valid. 
  if (parent == null) return null;

  T foundChild = null;

  int childrenCount = VisualTreeHelper.GetChildrenCount(parent);
  for (int i = 0; i < childrenCount; i++)
  {
    var child = VisualTreeHelper.GetChild(parent, i);
    // If the child is not of the request child type child
    T childType = child as T;
    if (childType == null)
    {
      // recursively drill down the tree
      foundChild = FindChild<T>(child, childName);

      // If the child is found, break so we do not overwrite the found child. 
      if (foundChild != null) break;
    }
    else if (!string.IsNullOrEmpty(childName))
    {
      var frameworkElement = child as FrameworkElement;
      // If the child's name is set for search
      if (frameworkElement != null && frameworkElement.Name == childName)
      {
        // if the child's name is of the request name
        foundChild = (T)child;
        break;
      }
    }
    else
    {
      // child element found.
      foundChild = (T)child;
      break;
    }
  }

  return foundChild;
}

Sebut saja seperti ini:

TextBox foundTextBox = 
   UIHelper.FindChild<TextBox>(Application.Current.MainWindow, "myTextBoxName");

Catatan Application.Current.MainWindowdapat berupa jendela induk.

CrimsonX
sumber
@CrimsonX: Mungkin saya melakukan ini salah ... Saya memiliki kebutuhan yang sama di mana saya harus mendapatkan kontrol (ListBox) di dalam ContentControl (Expander). Kode di atas tidak berfungsi untuk saya seperti saya .. Saya harus memperbarui kode di atas untuk melihat apakah node leaf (GetChildrenCount => 0) adalah ContentControl. Jika ya, periksa apakah kontennya sesuai dengan kriteria nama + jenis.
Gishu
@ Gishu - Saya pikir ini akan berhasil untuk tujuan ini. Bisakah Anda menyalin & menempelkan kode Anda untuk menunjukkan bagaimana Anda menggunakan panggilan? Saya berharap itu adalah FindChild <ListBox> (Expander myExpanderName, "myListBoxName").
CrimsonX
3
@ GrimsonX Saya pikir saya menemukan kasus sudut lain. Saya mencoba menemukan PART_SubmenuPlaceholder di RibbonApplicationMenuItem, tetapi kode di atas tidak berfungsi. Untuk mengatasinya, saya perlu menambahkan yang berikut: if (name == ElementName) else {foundChild = FindChild (child, name) if (foundChild! = Null) break; }
kevindaub
6
Harap berhati-hati, ada bug atau lebih dalam jawabannya. Ini akan berhenti segera setelah mencapai anak dari tipe yang dicari. Saya pikir Anda harus mempertimbangkan / memprioritaskan jawaban lain.
Eric Ouellet
2
Kode ini bagus, tetapi tidak akan berfungsi jika Anda tidak mencari jenis elemen tertentu, misalnya jika Anda lulus FrameworkElementsebagai T, itu akan mengembalikan nol segera setelah loop pertama berakhir. jadi Anda perlu melakukan beberapa modifikasi.
Amir Oveisi
131

Anda juga dapat menemukan elemen dengan nama menggunakan FrameworkElement.FindName (string) .

Diberikan:

<UserControl ...>
    <TextBlock x:Name="myTextBlock" />
</UserControl>

Dalam file kode-belakang, Anda dapat menulis:

var myTextBlock = (TextBlock)this.FindName("myTextBlock");

Tentu saja, karena didefinisikan menggunakan x: Name, Anda bisa mereferensikan bidang yang dihasilkan, tetapi mungkin Anda ingin mencarinya secara dinamis daripada statis.

Pendekatan ini juga tersedia untuk template, di mana item bernama muncul beberapa kali (sekali per penggunaan template).

Drew Noakes
sumber
6
Agar ini berfungsi, Anda tidak perlu menambahkan "x:" ke atribut nama.
brian buck
3
Ini sepertinya tidak selalu berhasil. Saya memiliki UserControls yang digabungkan bersama secara terprogram dalam grid bersarang sebagai konten dari jendela properti. Namun CrimsonX bekerja dengan baik.
Matt
4
Ini tidak akan berfungsi untuk elemen dalam ItemControls, ListBoxes, dll.
Sorensen
67

Anda dapat menggunakan VisualTreeHelper untuk menemukan kontrol. Di bawah ini adalah metode yang menggunakan VisualTreeHelper untuk menemukan kontrol induk dari tipe tertentu. Anda dapat menggunakan VisualTreeHelper untuk menemukan kontrol dengan cara lain juga.

public static class UIHelper
{
   /// <summary>
   /// Finds a parent of a given item on the visual tree.
   /// </summary>
   /// <typeparam name="T">The type of the queried item.</typeparam>
   /// <param name="child">A direct or indirect child of the queried item.</param>
   /// <returns>The first parent item that matches the submitted type parameter. 
   /// If not matching item can be found, a null reference is being returned.</returns>
   public static T FindVisualParent<T>(DependencyObject child)
     where T : DependencyObject
   {
      // get parent item
      DependencyObject parentObject = VisualTreeHelper.GetParent(child);

      // we’ve reached the end of the tree
      if (parentObject == null) return null;

      // check if the parent matches the type we’re looking for
      T parent = parentObject as T;
      if (parent != null)
      {
         return parent;
      }
      else
      {
         // use recursion to proceed with next level
         return FindVisualParent<T>(parentObject);
      }
   }
}

Sebut saja seperti ini:

Window owner = UIHelper.FindVisualParent<Window>(myControl);
John Myczek
sumber
Bagaimana Anda mendapatkan atau apa itu myControl?
Demodave
21

Saya mungkin hanya mengulangi semua orang tetapi saya memiliki sepotong kode cantik yang memperluas kelas DependencyObject dengan metode FindChild () yang akan memberi Anda anak berdasarkan jenis dan nama. Cukup sertakan dan gunakan.

public static class UIChildFinder
{
    public static DependencyObject FindChild(this DependencyObject reference, string childName, Type childType)
    {
        DependencyObject foundChild = null;
        if (reference != null)
        {
            int childrenCount = VisualTreeHelper.GetChildrenCount(reference);
            for (int i = 0; i < childrenCount; i++)
            {
                var child = VisualTreeHelper.GetChild(reference, i);
                // If the child is not of the request child type child
                if (child.GetType() != childType)
                {
                    // recursively drill down the tree
                    foundChild = FindChild(child, childName, childType);
                }
                else if (!string.IsNullOrEmpty(childName))
                {
                    var frameworkElement = child as FrameworkElement;
                    // If the child's name is set for search
                    if (frameworkElement != null && frameworkElement.Name == childName)
                    {
                        // if the child's name is of the request name
                        foundChild = child;
                        break;
                    }
                }
                else
                {
                    // child element found.
                    foundChild = child;
                    break;
                }
            }
        }
        return foundChild;
    }
}

Semoga bermanfaat.

Tri Q Tran
sumber
2
Per posting saya di atas, ada kesalahan implementasi kecil dalam kode Anda: stackoverflow.com/questions/636383/wpf-ways-to-find-controls/…
CrimsonX
18

Ekstensi saya ke kode.

  • Menambahkan kelebihan beban untuk menemukan satu anak berdasarkan jenis, berdasarkan jenis dan kriteria (predikat), temukan semua anak jenis yang memenuhi kriteria
  • metode FindChildren adalah iterator selain menjadi metode ekstensi untuk DependencyObject
  • FindChildren juga menjalankan sub-pohon yang logis. Lihat posting Josh Smith ditautkan di posting blog.

Sumber: https://code.google.com/p/gishu-util/source/browse/#git%2FWPF%2FUtilities

Posting blog penjelasan: http://madcoderspeak.blogspot.com/2010/04/wpf-find-child-control-of-specific-type.html

Gishu
sumber
-1 Persis apa yang akan saya terapkan (predikat, iterator, dan metode ekstensi), tetapi ada 404 pada tautan sumber. Akan berubah menjadi +1 jika kode disertakan di sini, atau tautan sumber diperbaiki!
cod3monk3y
@ cod3monk3y - Git migrasi sepertinya membunuh tautan :) Ini dia .. code.google.com/p/gishu-util/source/browse/…
Gishu
18

Jika Anda ingin menemukan SEMUA kontrol dari jenis tertentu, Anda mungkin tertarik dengan cuplikan ini juga

    public static IEnumerable<T> FindVisualChildren<T>(DependencyObject parent) 
        where T : DependencyObject
    {
        int childrenCount = VisualTreeHelper.GetChildrenCount(parent);
        for (int i = 0; i < childrenCount; i++)
        {
            var child = VisualTreeHelper.GetChild(parent, i);

            var childType = child as T;
            if (childType != null)
            {
                yield return (T)child;
            }

            foreach (var other in FindVisualChildren<T>(child))
            {
                yield return other;
            }
        }
    }
UrbanEsc
sumber
3
Bagus tapi pastikan kontrol dimuat jika tidak GetChildrenCount akan kembali 0.
Klaus Nji
@UrbanEsc, mengapa Anda melakukan childyang kedua kalinya? Jika Anda memiliki childTypetipe T, Anda dapat menulis di dalam if: yield return childType... tidak?
Massimiliano Kraus
@MassimilianoKraus Hei, maaf atas respons yang terlambat, tetapi Anda benar. Saya mengaitkannya dengan saya menulis ulang cuplikan ini beberapa kali, dan karenanya ini mungkin merupakan fragmen dari cek yang berbeda
UrbanEsc
16

Ini akan mengabaikan beberapa elemen - Anda harus memperluasnya seperti ini untuk mendukung beragam kontrol. Untuk diskusi singkat, lihat di sini

 /// <summary>
 /// Helper methods for UI-related tasks.
 /// </summary>
 public static class UIHelper
 {
   /// <summary>
   /// Finds a parent of a given item on the visual tree.
   /// </summary>
   /// <typeparam name="T">The type of the queried item.</typeparam>
   /// <param name="child">A direct or indirect child of the
   /// queried item.</param>
   /// <returns>The first parent item that matches the submitted
   /// type parameter. If not matching item can be found, a null
   /// reference is being returned.</returns>
   public static T TryFindParent<T>(DependencyObject child)
     where T : DependencyObject
   {
     //get parent item
     DependencyObject parentObject = GetParentObject(child);

     //we've reached the end of the tree
     if (parentObject == null) return null;

     //check if the parent matches the type we're looking for
     T parent = parentObject as T;
     if (parent != null)
     {
       return parent;
     }
     else
     {
       //use recursion to proceed with next level
       return TryFindParent<T>(parentObject);
     }
   }

   /// <summary>
   /// This method is an alternative to WPF's
   /// <see cref="VisualTreeHelper.GetParent"/> method, which also
   /// supports content elements. Do note, that for content element,
   /// this method falls back to the logical tree of the element!
   /// </summary>
   /// <param name="child">The item to be processed.</param>
   /// <returns>The submitted item's parent, if available. Otherwise
   /// null.</returns>
   public static DependencyObject GetParentObject(DependencyObject child)
   {
     if (child == null) return null;
     ContentElement contentElement = child as ContentElement;

     if (contentElement != null)
     {
       DependencyObject parent = ContentOperations.GetParent(contentElement);
       if (parent != null) return parent;

       FrameworkContentElement fce = contentElement as FrameworkContentElement;
       return fce != null ? fce.Parent : null;
     }

     //if it's not a ContentElement, rely on VisualTreeHelper
     return VisualTreeHelper.GetParent(child);
   }
}
Philipp
sumber
5
Dengan konvensi, saya harapkan setiap Try*metode untuk kembali booldan memiliki outparameter yang kembali jenis yang bersangkutan, seperti:bool IDictionary.TryGetValue(TKey key, out TValue value)
Drew Noakes
@DakeNoakes, apa yang Anda sarankan untuk memanggilnya? Juga, bahkan dengan harapan seperti itu saya menemukan kodenya jelas dan jelas untuk digunakan.
ANeves
1
@ ANeves, dalam hal ini saya hanya akan menyebutnya FindParent. Nama ini bagi saya menyiratkan bahwa itu bisa kembali null. The Try*prefix digunakan di seluruh BCL dengan cara saya jelaskan di atas. Perhatikan juga bahwa sebagian besar jawaban lain di sini menggunakan Find*konvensi penamaan. Ini hanya poin kecil :)
Drew Noakes
16

Saya mengedit kode CrimsonX karena tidak bekerja dengan jenis superclass:

public static T FindChild<T>(DependencyObject depObj, string childName)
   where T : DependencyObject
{
    // Confirm obj is valid. 
    if (depObj == null) return null;

    // success case
    if (depObj is T && ((FrameworkElement)depObj).Name == childName)
        return depObj as T;

    for (int i = 0; i < VisualTreeHelper.GetChildrenCount(depObj); i++)
    {
        DependencyObject child = VisualTreeHelper.GetChild(depObj, i);

        //DFS
        T obj = FindChild<T>(child, childName);

        if (obj != null)
            return obj;
    }

    return null;
}
andresp
sumber
1
Jika Anda melewati metode ini DependencyObject, ini bukan metode FrameworkElementyang dapat membuang Pengecualian. Juga menggunakan GetChildrenCountpada setiap iterasi dari forloop terdengar seperti ide yang buruk.
Tim Pohlmann
1
baik, ini dari 5 tahun yang lalu, jadi saya bahkan tidak tahu apakah itu berfungsi lagi :)
andresp
Saya hanya menyebutkannya, karena saya menemukan itu dan yang lainnya juga bisa;)
Tim Pohlmann
13

Sementara saya suka rekursi secara umum, itu tidak seefisien iterasi ketika pemrograman dalam C #, jadi mungkin solusi berikut ini lebih rapi daripada yang disarankan oleh John Myczek? Ini mencari hierarki dari kontrol yang diberikan untuk menemukan kontrol leluhur dari jenis tertentu.

public static T FindVisualAncestorOfType<T>(this DependencyObject Elt)
    where T : DependencyObject
{
    for (DependencyObject parent = VisualTreeHelper.GetParent(Elt);
        parent != null; parent = VisualTreeHelper.GetParent(parent))
    {
        T result = parent as T;
        if (result != null)
            return result;
    }
    return null;
}

Sebut saja seperti ini untuk menemukan Windowkontrol yang disebut ExampleTextBox:

Window window = ExampleTextBox.FindVisualAncestorOfType<Window>();
Nathan Phillips
sumber
9

Inilah kode saya untuk menemukan kontrol berdasarkan Jenis sambil mengontrol seberapa dalam kita masuk ke hierarki (maxDepth == 0 berarti jauh lebih dalam).

public static class FrameworkElementExtension
{
    public static object[] FindControls(
        this FrameworkElement f, Type childType, int maxDepth)
    {
        return RecursiveFindControls(f, childType, 1, maxDepth);
    }

    private static object[] RecursiveFindControls(
        object o, Type childType, int depth, int maxDepth = 0)
    {
        List<object> list = new List<object>();
        var attrs = o.GetType()
            .GetCustomAttributes(typeof(ContentPropertyAttribute), true);
        if (attrs != null && attrs.Length > 0)
        {
            string childrenProperty = (attrs[0] as ContentPropertyAttribute).Name;
            foreach (var c in (IEnumerable)o.GetType()
                .GetProperty(childrenProperty).GetValue(o, null))
            {
                if (c.GetType().FullName == childType.FullName)
                    list.Add(c);
                if (maxDepth == 0 || depth < maxDepth)
                    list.AddRange(RecursiveFindControls(
                        c, childType, depth + 1, maxDepth));
            }
        }
        return list.ToArray();
    }
}
exciton80
sumber
9

exciton80 ... Saya mengalami masalah dengan kode Anda tidak berulang melalui usercontrols. Itu mengenai root Grid dan melempar kesalahan. Saya percaya ini memperbaikinya untuk saya:

public static object[] FindControls(this FrameworkElement f, Type childType, int maxDepth)
{
    return RecursiveFindControls(f, childType, 1, maxDepth);
}

private static object[] RecursiveFindControls(object o, Type childType, int depth, int maxDepth = 0)
{
    List<object> list = new List<object>();
    var attrs = o.GetType().GetCustomAttributes(typeof(ContentPropertyAttribute), true);
    if (attrs != null && attrs.Length > 0)
    {
        string childrenProperty = (attrs[0] as ContentPropertyAttribute).Name;
        if (String.Equals(childrenProperty, "Content") || String.Equals(childrenProperty, "Children"))
        {
            var collection = o.GetType().GetProperty(childrenProperty).GetValue(o, null);
            if (collection is System.Windows.Controls.UIElementCollection) // snelson 6/6/11
            {
                foreach (var c in (IEnumerable)collection)
                {
                    if (c.GetType().FullName == childType.FullName)
                        list.Add(c);
                    if (maxDepth == 0 || depth < maxDepth)
                        list.AddRange(RecursiveFindControls(
                            c, childType, depth + 1, maxDepth));
                }
            }
            else if (collection != null && collection.GetType().BaseType.Name == "Panel") // snelson 6/6/11; added because was skipping control (e.g., System.Windows.Controls.Grid)
            {
                if (maxDepth == 0 || depth < maxDepth)
                    list.AddRange(RecursiveFindControls(
                        collection, childType, depth + 1, maxDepth));
            }
        }
    }
    return list.ToArray();
}
Shawn Nelson
sumber
8

Saya memiliki fungsi urutan seperti ini (yang sepenuhnya umum):

    public static IEnumerable<T> SelectAllRecursively<T>(this IEnumerable<T> items, Func<T, IEnumerable<T>> func)
    {
        return (items ?? Enumerable.Empty<T>()).SelectMany(o => new[] { o }.Concat(SelectAllRecursively(func(o), func)));
    }

Mendapatkan anak langsung:

    public static IEnumerable<DependencyObject> FindChildren(this DependencyObject obj)
    {
        return Enumerable.Range(0, VisualTreeHelper.GetChildrenCount(obj))
            .Select(i => VisualTreeHelper.GetChild(obj, i));
    }

Menemukan semua anak di bawah pohon hiararkis:

    public static IEnumerable<DependencyObject> FindAllChildren(this DependencyObject obj)
    {
        return obj.FindChildren().SelectAllRecursively(o => o.FindChildren());
    }

Anda dapat memanggil ini di Window untuk mendapatkan semua kontrol.

Setelah Anda memiliki koleksi, Anda dapat menggunakan LINQ (yaitu OfType, Di mana).

VB Guy
sumber
6

Karena pertanyaannya cukup umum sehingga mungkin menarik orang mencari jawaban untuk kasus-kasus yang sangat sepele: jika Anda hanya ingin anak daripada keturunan, Anda dapat menggunakan Linq:

private void ItemsControlItem_Loaded(object sender, RoutedEventArgs e)
{
    if (SomeCondition())
    {
        var children = (sender as Panel).Children;
        var child = (from Control child in children
                 where child.Name == "NameTextBox"
                 select child).First();
        child.Focus();
    }
}

atau tentu saja jelas untuk loop berulang atas Anak.

El Zorko
sumber
3

Opsi ini sudah berbicara tentang melintasi Pohon Visual di C #. Mungkin untuk melintasi pohon visual dalam xaml juga menggunakan ekstensi markup RelativeSource. msdn

temukan berdasarkan tipe

Binding="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type <TypeToFind>}}}" 
Neeraj
sumber
2

Berikut ini solusi yang menggunakan predikat fleksibel:

public static DependencyObject FindChild(DependencyObject parent, Func<DependencyObject, bool> predicate)
{
    if (parent == null) return null;

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

        if (predicate(child))
        {
            return child;
        }
        else
        {
            var foundChild = FindChild(child, predicate);
            if (foundChild != null)
                return foundChild;
        }
    }

    return null;
}

Misalnya Anda bisa menyebutnya seperti ini:

var child = FindChild(parent, child =>
{
    var textBlock = child as TextBlock;
    if (textBlock != null && textBlock.Name == "MyTextBlock")
        return true;
    else
        return false;
}) as TextBlock;
Tim Pohlmann
sumber
1

Kode ini hanya memperbaiki bug jawaban @CrimsonX:

 public static T FindChild<T>(DependencyObject parent, string childName)
       where T : DependencyObject
    {    
      // Confirm parent and childName are valid. 
      if (parent == null) return null;

      T foundChild = null;

      int childrenCount = VisualTreeHelper.GetChildrenCount(parent);
      for (int i = 0; i < childrenCount; i++)
      {
        var child = VisualTreeHelper.GetChild(parent, i);
        // If the child is not of the request child type child
        T childType = child as T;
        if (childType == null)
        {
          // recursively drill down the tree
          foundChild = FindChild<T>(child, childName);

          // If the child is found, break so we do not overwrite the found child. 
          if (foundChild != null) break;
        }
        else if (!string.IsNullOrEmpty(childName))
        {
          var frameworkElement = child as FrameworkElement;
          // If the child's name is set for search
          if (frameworkElement != null && frameworkElement.Name == childName)
          {
            // if the child's name is of the request name
            foundChild = (T)child;
            break;
          }

 // recursively drill down the tree
          foundChild = FindChild<T>(child, childName);

          // If the child is found, break so we do not overwrite the found child. 
          if (foundChild != null) break;


        else
        {
          // child element found.
          foundChild = (T)child;
          break;
        }
      }

      return foundChild;
    }  

Anda hanya perlu terus memanggil metode secara rekursif jika jenisnya cocok tetapi nama tidak (ini terjadi ketika Anda lulus FrameworkElementsebagai T). kalau tidak itu akan kembali nulldan itu salah.

Amir Oveisi
sumber
0

Untuk menemukan leluhur tipe tertentu dari kode, Anda dapat menggunakan:

[CanBeNull]
public static T FindAncestor<T>(DependencyObject d) where T : DependencyObject
{
    while (true)
    {
        d = VisualTreeHelper.GetParent(d);

        if (d == null)
            return null;

        var t = d as T;

        if (t != null)
            return t;
    }
}

Implementasi ini menggunakan iterasi alih-alih rekursi yang bisa sedikit lebih cepat.

Jika Anda menggunakan C # 7, ini bisa dibuat sedikit lebih pendek:

[CanBeNull]
public static T FindAncestor<T>(DependencyObject d) where T : DependencyObject
{
    while (true)
    {
        d = VisualTreeHelper.GetParent(d);

        if (d == null)
            return null;

        if (d is T t)
            return t;
    }
}
Drew Noakes
sumber
-5

Coba ini

<TextBlock x:Name="txtblock" FontSize="24" >Hai Welcom to this page
</TextBlock>

Kode di Balik

var txtblock = sender as Textblock;
txtblock.Foreground = "Red"
Jayasri
sumber