WPF denetimlerini ada veya türe göre nasıl bulabilirim?


264

Belirli bir ad veya tür ile eşleşen denetimler için bir WPF denetim hiyerarşisi aramak gerekir. Bunu nasıl yapabilirim?

Yanıtlar:


311

Herhangi bir ebeveyn üzerinde kullanılabilecek bir findChild Algoritması oluşturmak için yukarıdaki John Myczek ve Tri Q algoritması tarafından kullanılan şablon formatını birleştirdim. Bir ağacı tekrar tekrar aşağı doğru aramanın uzun bir süreç olabileceğini unutmayın. Ben sadece bir WPF uygulamasında bu nokta kontrol ettik, lütfen bulabileceğiniz herhangi bir hata hakkında yorum ve kodumu düzeltirim.

WPF Snoop görsel ağaca bakarken faydalı bir araçtır - işinizi kontrol etmek için test ederken veya bu algoritmayı kullanırken şiddetle tavsiye ederim.

Tri Q Algoritmasında küçük bir hata var. Çocuk bulunduktan sonra childrenCount değeri> 1 ise ve tekrar edersek, düzgün bulunan çocuğun üzerine yazabiliriz. Bu nedenle if (foundChild != null) break;bu koşulla başa çıkmak için koduma bir ekledim .

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

Buna şöyle deyin:

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

Not Application.Current.MainWindowherhangi bir üst pencere olabilir.


@CrimsonX: Belki de bu yanlış yapıyorum ... Bir ContentControl (Genişletici) içinde bir kontrol (ListBox) almak için gereken yerde benzer bir ihtiyaç vardı. Yukarıdaki kod benim için olduğu gibi çalışmadı .. Bir yaprak düğüm (GetChildrenCount => 0) bir ContentControl olup olmadığını görmek için yukarıdaki kodu güncellemek zorunda kaldı. Evetse, içeriğin ad + tür ölçütleriyle eşleşip eşleşmediğini kontrol edin.
Mart'ta Gishu

@Gishu - Bence bu amaçla çalışmalı. Aramayı nasıl kullandığınızı göstermek için kodunuzu kopyalayıp yapıştırabilir misiniz? Ben FindChild <ListBox> (Expander myExpanderName, "myListBoxName") olması beklenir.
CrimsonX

3
@CrimsonX Sanırım başka bir köşe davası buldum. RibbonApplicationMenuItem PART_SubmenuPlaceholder bulmaya çalışıyordum, ancak yukarıdaki kod çalışma değildi. Çözmek için aşağıdakileri eklemem gerekiyordu: if (name == ElementName) else {foundChild = FindChild (çocuk, isim) if (foundChild! = Null) break; }
kevindaub

6
Lütfen dikkatli olun, cevapta bir veya daha fazla hata var. Aranan tipteki bir çocuğa ulaşır ulaşmaz duracaktır. Bence diğer cevapları düşünmeli / önceliklendirmelisiniz.
Eric Ouellet

2
Bu kod harika, ancak belirli bir öğe türü aramıyorsanız işe yaramaz, örneğin FrameworkElementT olarak iletirseniz, ilk döngü biter bitmez null değerini döndürür. bu yüzden bazı değişiklikler yapmanız gerekecek.
Amir Oveisi

131

FrameworkElement.FindName (string) öğesini kullanarak bir öğeyi ada göre de bulabilirsiniz .

Verilen:

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

Arkasındaki kod dosyasına şunları yazabilirsiniz:

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

Elbette, x: Name kullanılarak tanımlandığından, oluşturulan alana referans verebilirsiniz, ancak belki de statik olarak değil dinamik olarak bakmak istersiniz.

Bu yaklaşım, adlandırılan öğenin birden çok kez göründüğü şablonlar için de kullanılabilir (şablonun kullanımı başına bir kez).


6
Bunun çalışması için ad özelliğine "x:" eklemeniz gerekmez.
brian buck

3
Bu her zaman işe yaramıyor gibi görünüyor. Birlikte bir özellikler penceresinin içeriği olarak iç içe ızgaralarda programlı olarak birleştirilen UserControls var. Ancak CrimsonX'in cevabı iyi çalışıyor.
Matt

4
Bu ItemControls, ListBoxes, vb. Öğeler için işe yaramaz
Sorensen

67

Denetimleri bulmak için VisualTreeHelper kullanabilirsiniz . Aşağıda, belirtilen türde bir üst denetim bulmak için VisualTreeHelper kullanan bir yöntem bulunmaktadır. Denetimleri başka şekillerde de bulmak için VisualTreeHelper kullanabilirsiniz.

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

Buna şöyle deyin:

Window owner = UIHelper.FindVisualParent<Window>(myControl);

Nasıl edinirim veya myControl nedir?
15'te Demodave

21

Ben sadece herkesi tekrar ediyor olabilirim ama size tip ve ada göre çocuğu alacak bir yöntem FindChild () ile DependencyObject sınıfını genişleten güzel bir kod parçası var. Sadece dahil edin ve kullanın.

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

Umarım faydalı bulursunuz.


2
Yukarıdaki
yazıma göre

18

Koda uzantılarım.

  • Türüne, türe ve ölçütlere göre (yüklem) bulmak için bir çocuğu bulmak için aşırı yükler eklendi, ölçütleri karşılayan türdeki tüm çocukları bulun
  • FindChildren yöntemi, DependencyObject için bir uzantı yöntemi olmanın yanı sıra bir yineleyicidir
  • FindChildren mantıksal alt ağaçlarda da yürüyor. Blog yazısında Josh Smith'in yayınına bakın.

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

Açıklayıcı blog yazısı: http://madcoderspeak.blogspot.com/2010/04/wpf-find-child-control-of-specific-type.html


-1 Tam olarak uygulamak üzere olduğum şey (yüklem, yineleyici ve uzantı yöntemi), ancak kaynak bağlantıda bir 404 var. Kod buraya eklenirse +1 olarak değişir veya kaynak bağlantısı sabittir!
cod3monk3y

@ cod3monk3y - Git geçişi göründüğü bağlantıyı öldürdü :) Buyrun .. code.google.com/p/gishu-util/source/browse/…
Gishu

18

Belirli bir türdeki TÜM denetimleri bulmak istiyorsanız, bu snippet ile de ilgilenebilirsiniz

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

3
İyi ama kontrolün yüklendiğinden emin olun aksi takdirde GetChildrenCount 0 döndürecektir.
Klaus Nji

@UrbanEsc, neden childikinci kez oynuyorsun ? Eğer childTypetürünüz Tvarsa, içine yazabilirsiniz if: yield return childType... hayır?
Massimiliano Kraus

@MassimilianoKraus Hey, geç cevap verdiğim için üzgünüm, ama haklısın. Bana bu parçacığı birkaç kez yeniden
yazmama bağlıyorum

16

Bu, bazı öğeleri kapatacaktır - daha geniş bir kontrol dizisini desteklemek için bu şekilde genişletmelisiniz. Kısa bir tartışma için buraya bir göz atın

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

5
Kural olarak, herhangi bir Try*yöntemin dönmesini boolve outsöz konusu türü döndüren bir parametreye sahip olmasını bool IDictionary.TryGetValue(TKey key, out TValue value)
beklerim

@DrewNoakes Philipp'e ne demesini önerirsiniz? Ayrıca, böyle bir beklentiyle bile, kodunu hem açık hem de açık olarak kullanıyorum.
ANeves

1
@Hayvanlar, bu durumda ben sadece derdim FindParent. Bu isim bana geri dönebileceğini ima ediyor null. Ön Try*ek, yukarıda açıkladığım şekilde BCL boyunca kullanılır. Ayrıca, buradaki diğer yanıtların çoğunun Find*adlandırma kuralını kullandığını unutmayın . Gerçi sadece küçük bir nokta :)
Drew Noakes

16

Süper sınıf türleriyle çalışmadığı için CrimsonX kodunu düzenledim:

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

1
Bu yöntemi a DependencyObjectolmayan bir yöntemden geçerseniz , FrameworkElementbir İstisna atabilir. Ayrıca döngünün GetChildrenCounther yinelemesinde kullanmak forkötü bir fikir gibi geliyor.
Tim Pohlmann

1
Peki, bu 5 yıl önce, bu yüzden artık işe
yarayıp yaramadığını

Ben sadece bahsetmiştim, çünkü ben tökezledi ve diğer de olabilir;)
Tim Pohlmann

13

Genel olarak özyinelemeyi sevmeme rağmen, C #'da programlama yaparken yineleme kadar verimli değil, bu yüzden belki de aşağıdaki çözüm John Myczek tarafından önerilenlerden daha temiz mi? Bu, belirli bir türün üst denetimini bulmak için belirli bir denetimden bir hiyerarşi arar.

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

Şu Windowadlı bir denetimi bulmak için buna ExampleTextBoxşöyle deyin :

Window window = ExampleTextBox.FindVisualAncestorOfType<Window>();

9

İşte, hiyerarşiye ne kadar derine girdiğimizi kontrol ederken Type'a göre kontrolleri bulmak için kodum (maxDepth == 0 sonsuz derin anlamına gelir).

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

9

exciton80 ... Ben usercontrols üzerinden yinelenen kod ile ilgili bir sorun yaşıyordu. Izgara köküne vuruyor ve bir hata atıyordu. Bunun benim için düzelttiğine inanıyorum:

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

8

(Tamamen genel) böyle bir dizi işlevi var:

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

Acil çocuk sahibi olma:

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

Hiyararşik ağacın altındaki tüm çocukları bulmak:

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

Tüm kontrolleri almak için bunu Pencereden çağırabilirsiniz.

Koleksiyona sahip olduktan sonra, LINQ (yani OfType, Where) kullanabilirsiniz.


6

Soru, çok önemsiz vakalara cevap arayan insanları çekebilecek kadar genel olduğundan: soyundan ziyade sadece bir çocuk istiyorsanız, Linq'i kullanabilirsiniz:

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

veya elbette Çocuklar üzerinde döngü yinelemeleri için aşikâr.


3

Bu seçenekler zaten C # 'da Görsel Ağacın üzerinden geçmekten bahsediyor. RelativeSource işaretleme uzantısını kullanarak xaml'de görsel ağacı da dolaşmak mümkündür. msdn

türe göre bul

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

2

İşte esnek yüklem kullanan bir çözüm:

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

Örneğin şöyle diyebilirsiniz:

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

1

Bu kod sadece @CrimsonX yanıtlayıcının hatasını giderir:

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

Türler eşleşiyor, ancak adlar eşleşmiyorsa yöntemi özyinelemeli olarak çağırmaya devam etmeniz gerekir (bu, FrameworkElementolarak geçtiğinizde olur T). aksi halde geri dönecek nullve bu yanlış.


0

Koddan belirli bir türde bir ata bulmak için şunları kullanabilirsiniz:

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

Bu uygulama, biraz daha hızlı olabilen özyineleme yerine yineleme kullanır.

C # 7 kullanıyorsanız, bu biraz daha kısa yapılabilir:

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

-5

Bunu dene

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

Arkasındaki Kod

var txtblock = sender as Textblock;
txtblock.Foreground = "Red"
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.