Encontre todos os controles na janela WPF por tipo

218

Estou procurando uma maneira de encontrar todos os controles no Windows por tipo,

por exemplo: encontre tudo TextBoxes, encontre todos os controles implementando uma interface específica etc.

Andrija
fonte
enquanto nós estamos sobre o tema, este também é relevante goo.gl/i9RVx
Andrija
Também escrevi uma postagem no blog sobre o tópico: Modificando um ControlTemplate em tempo de execução
Adolfo Perez

Respostas:

430

Isso deve fazer o truque

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

então você enumera sobre os controles assim

foreach (TextBlock tb in FindVisualChildren<TextBlock>(window))
{
    // do something with tb here
}
Bryce Kahle
fonte
68
Nota: Se você estiver tentando fazer isso funcionar e descobrir que sua janela (por exemplo) possui 0 filhos visuais, tente executar este método no manipulador de eventos Loaded. Se você executá-lo no construtor (mesmo após InitializeComponent ()), os filhos visuais ainda não foram carregados e não funcionarão.
22613 Ryan Lundy
24
A mudança do VisualTreeHelper para o LogicalTreeHelpers também fará com que os elementos invisíveis sejam incluídos.
Mathias Lykkegaard Lorenzen
11
A linha "filho! = Nulo && filho é T" é redundante? Não deveria apenas ler "criança é T"
meio-
1
Eu iria transformá-lo em um método de extensão com apenas insering um thisantes DependencyObject=>this DependencyObject depObj
Johannes Wanzek
1
@JohannesWanzek Não se esqueça de que você também precisará alterar a parte em que você chama a criança: foreach (ChildofChild.FindVisualChildren <T> ()) {bla bla bla}
será
66

Esta é a maneira mais fácil:

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

onde controle é o elemento raiz da janela.

Joel
fonte
1
o que você quer dizer com "elemento raiz"? O que devo escrever para me conectar ao meu formulário da janela principal?
deadfish
Eu obtê-lo, em XAML ver que eu tinha que nome do conjunto de grade <Grid Name="Anata_wa_yoru_o_shihai_suru_ai">here buttons</Grid>e, em seguida, eu poderia usarAnata_wa_yoru_o_shihai_suru_ai.Children.OfType<myType>();
deadfish
68
Isso não responde à pergunta que foi feita. Ele retorna apenas filho controla um nível de profundidade.
6133 Jim
21

Adaptei a resposta de @Bryce Kahle para seguir a sugestão e o uso de @Mathias Lykkegaard Lorenzen LogicalTreeHelper.

Parece funcionar bem. ;)

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

(Ele ainda não verifica os controles da guia ou grades dentro dos GroupBoxes, conforme mencionado por @Benjamin Berry e @David R, respectivamente.) (Também seguiu a sugestão de @ noonand e removeu o filho redundante! = Null)

Simon F
fonte
foi à procura de um tempo como limpar todas as caixas de meu texto, eu tenho várias abas e este é o único código que funcionou :) Obrigado
JohnChris
13

Use as classes auxiliares VisualTreeHelperou LogicalTreeHelperdependendo da árvore em que estiver interessado. Ambos fornecem métodos para obter os filhos de um elemento (embora a sintaxe seja um pouco diferente). Costumo usar essas classes para encontrar a primeira ocorrência de um tipo específico, mas você pode modificá-lo facilmente para encontrar todos os objetos desse tipo:

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
fonte
+1 para explicação e postagem, mas Bryce Kahle postou uma função que funciona totalmente Obrigado
Andrija
Isso não resolve o problema da pergunta, e também a resposta com o tipo genérico é muito mais clara. Combiná-lo com o uso de VisualTreeHelper.GetChildrenCount (obj) corrigirá o problema. No entanto, é útil ser considerado como uma opção.
Vasil Popov
9

Descobri que a linha, VisualTreeHelper.GetChildrenCount(depObj);usada em vários exemplos acima, não retorna uma contagem diferente de zero para GroupBoxes, em particular, onde os elementos GroupBoxcontém um Gride os Gridfilhos. Acredito que isso possa ocorrer porque GroupBoxnão é permitido que contenha mais de um filho e isso esteja armazenado em sua Contentpropriedade. Não há nenhum GroupBox.Childrentipo de propriedade. Tenho certeza de que não fiz isso com muita eficiência, mas modifiquei o primeiro exemplo "FindVisualChildren" nessa cadeia da seguinte maneira:

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
fonte
4

Para obter uma lista de todos os filhos de um tipo específico, você pode usar:

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
fonte
4

Pequena alteração na recursão para, por exemplo, você pode encontrar o controle de guia filho de um controle de guia.

    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
fonte
3

Aqui está mais uma versão compacta, com a sintaxe genérica:

    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;
        }
    }
user1656671
fonte
2

E é assim que funciona para cima

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

fonte
2

Observe que o uso do VisualTreeHelper funciona apenas em controles derivados do Visual ou Visual3D. Se você também precisar inspecionar outros elementos (por exemplo, TextBlock, FlowDocument etc.), o uso do VisualTreeHelper gerará uma exceção.

Aqui está uma alternativa que retorna à árvore lógica, se necessário:

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

Philipp
fonte
1

Queria adicionar um comentário, mas tenho menos de 50 pts para poder apenas "Responder". Esteja ciente de que se você usar o método "VisualTreeHelper" para recuperar objetos "TextBlock" XAML, ele também pegará objetos "Button" XAML. Se você reinicializar o objeto "TextBlock" escrevendo no parâmetro Textblock.Text, não será mais possível alterar o texto do botão usando o parâmetro Button.Content. O botão mostrará permanentemente o texto gravado a partir da ação Textblock.Text write (a partir de quando foi recuperado -

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

Para contornar isso, você pode tentar usar um "TextBox" XAML e adicionar métodos (ou eventos) para imitar um botão XAMAL. XAML "TextBox" não é coletado por uma pesquisa por "TextBlock".

Lifygen
fonte
Essa é a diferença entre a árvore visual e a lógica. A árvore visual contém todos os controles (incluindo aqueles de que é feito um controle, definidos no modelo de controles), enquanto a árvore lógica contém apenas os controles reais (sem aqueles definidos nos modelos). Há uma boa visualização desse conceito aqui: link
lauxjpn
1

Minha versão para 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
fonte
1

Por alguma razão, nenhuma das respostas postadas aqui me ajudou a obter todos os controles de um determinado tipo contidos em um determinado controle na minha MainWindow. Eu precisava encontrar todos os itens de menu em um menu para iterá-los. Eles não eram todos descendentes diretos do menu, então eu consegui coletar apenas o primeiro número deles usando qualquer um dos códigos acima. Este método de extensão é a minha solução para o problema de qualquer pessoa que continue lendo até aqui.

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

Espero que ajude.

αNerd
fonte
1

A resposta aceita retorna os elementos descobertos mais ou menos desordenados , seguindo o primeiro ramo filho o mais profundo possível, enquanto produz os elementos descobertos ao longo do caminho, antes de voltar atrás e repetir as etapas para galhos de árvores ainda não analisados.

Se você precisar dos elementos descendentes em ordem decrescente , onde os filhos diretos serão produzidos primeiro, depois os filhos e assim por diante, o seguinte algoritmo funcionará:

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

Os elementos resultantes serão ordenados do mais próximo para o mais distante. Isso será útil, por exemplo, se você estiver procurando pelo elemento filho mais próximo de algum tipo e condição:

var foundElement = GetDescendants<StackPanel>(someElement)
                       .FirstOrDefault(o => o.SomeProperty == SomeState);
lauxjpn
fonte
1
Está faltando alguma coisa; childestá indefinido.
Codebender
1

@ Bryce, resposta muito boa.

Versão do 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

Uso (isso desativa todos os TextBoxes em uma janela):

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

Achei mais fácil sem os Auxiliares da Árvore Visual:

foreach (UIElement element in MainWindow.Children) {
    if (element is TextBox) { 
        if ((element as TextBox).Text != "")
        {
            //Do something
        }
    }
};
Rafael Ventura
fonte
3
Isso vai apenas um nível profundo. no XAML, você possui controles profundamente aninhados.
SQL Police