タイプ別にWPFウィンドウのすべてのコントロールを検索する


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
注:これを機能させようとしていて、ウィンドウ(たとえば)に視覚的な子がないことがわかった場合は、Loadedイベントハンドラーでこのメソッドを実行してみてください。これをコンストラクターで実行すると(InitializeComponent()の後でも)、視覚的な子はまだ読み込まれておらず、機能しません。
ライアンランディ

24
VisualTreeHelperからLogicalTreeHelpersに切り替えると、非表示の要素も含まれます。
Mathias Lykkegaard Lorenzen 2012

11
「child!= null && child is T」という行は冗長ではありませんか?「child is T」とだけ読むべきではない
正午

1
私はちょうどinseringで拡張メソッドにそれを回すだろうthisDependencyObject=>this DependencyObject depObj
ヨハネスWanzek

1
@JohannesWanzek子で呼び出す場所を変更する必要があることも忘れないでください:foreach(ChildofChild.FindVisualChildren <T>()){bla bla bla}
Will

66

これが最も簡単な方法です。

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

ここで、controlはウィンドウのルート要素です。


1
「ルート要素」とはどういう意味ですか?メインウィンドウフォームに接続するには何を書けばよいですか?
デッドフィッシュ2011

私はそれを取得し、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
これは、尋ねられた質問には答えません。子コントロールを1レベルだけ返します。
ジム

21

@Mathias Lykkegaard Lorenzenの提案と使用に従って、@ Bryce Kahleの回答を採用しました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;
                }
            }
        }
    }
}

(@Benjamin Berryと@David Rでそれぞれ言及されているように、GroupBoxes内のタブコントロールまたはグリッドはまだチェックされません。)(@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は完全に感謝を働く機能掲示
Andrija

これは問題の問題を解決するものではなく、ジェネリック型での答えもはるかに明確です。これをVisualTreeHelper.GetChildrenCount(obj)の使用と組み合わせると、問題が解決します。ただし、オプションとして検討すると便利です。
Vasil Popov

9

私はラインが、ことがわかったVisualTreeHelper.GetChildrenCount(depObj);ために非ゼロカウントを返さない上に、いくつかの例で使用しGroupBoxた場合、具体的には、ES 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 "Button"オブジェクトも取得することに注意してください。Textblock.Textパラメータに書き込んで「TextBlock」オブジェクトを再初期化すると、Button.Contentパラメータを使用してボタンテキストを変更できなくなります。ボタンは、Textblock.Text書き込みアクションから(それに取得されたときから)書き込まれたテキストを永続的に表示します-

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 "TextBox"を使用して、XAMALボタンを模倣するメソッド(またはイベント)を追加します。XAML "TextBox"は、 "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

何らかの理由で、ここに投稿された回答のどれも、メインウィンドウの特定のコントロールに含まれる特定のタイプのすべてのコントロールを取得するのに役立ちませんでした。繰り返し処理するために、1つのメニューですべてのメニュー項目を見つける必要がありました。それらはすべてメニューの直接の子孫ではなかったので、上記のコードのいずれかを使用して、それらの最初のlileのみを収集することができました。この拡張メソッドは、ここまでずっと読み続ける人のための問題の私の解決策です。

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

使用法(これにより、ウィンドウ内のすべてのTextBoxが無効になります):

        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
これは1レベルだけの深さになります。XAMLでは、深くネストされたコントロールがあります。
SQLポリス
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.