'How do I access all XAML elements with a given tag in C# WPF?

In my XAML, I have various error messages in the following structure:

    <Border CornerRadius="3" Background="#FFF3C7C7" Margin="6" Visibility="Collapsed" Name="quick_error" Tag="err_box">
        <TextBlock Name="err_msg" Foreground="#FFFD3434" TextWrapping="Wrap" Margin="6" ></TextBlock>
    </Border>

The blocks will be identical other than Name.

I'm trying to write code to turn off all visible errors at once. To do that, I'm using a recursive function to iterate over all child elements of my main container and then look for any children from there and iterate/recurse over them as well (because the errors can be at any depth and inside any combination of parent containers).


        private void ShowError(string errName,string errMsg)
        {
            Border errBox = (Border) mainApp.FindName(errName);
            errBox.Visibility = Visibility.Visible;
            TextBlock msg = (TextBlock) errBox.FindName("err_msg");
            msg.Text = errMsg; 
        }

        private void RecurseChildren(DependencyObject child)
        {
            if (child is Border)
            {
                Border temp = (Border)child;
            
                if (temp.Tag == "err_box")
                    ShowError("quick_error", temp.Name);
            }
            for(var i = 0; i < VisualTreeHelper.GetChildrenCount(child); i++)
            {
                RecurseChildren(VisualTreeHelper.GetChild(child, i));
            }           
        }

        private void TestRecurse()
        {
            foreach (UIElement child in mainApp.Children)
            {
                RecurseChildren(child);
            }
        }

For now, the above code is designed to show a message in one error box (named "quick_error") as a test. I'm trying to get it to show the name of the borders it finds (I'm aware the names will be overwritten by the next border found, but that's enough to see it's working so that's fine).

But for some reason, nothing shows. As a test, I removed the if statement, but then the value that shows in the error box is "border" which is not the name of any border in my XAML. I have two border values, one named "temp" and one named "quick_error".

I tried showing the tagname only, but if I do that...

ShowError("quick_error", (string)temp.Tag);

... it just comes up blank. To test, I assigned a tag to my other border so both have a tag but it's still blank. So neither tag nor name are returning expected values. What's am I doing wrong?



Solution 1:[1]

I found an answer that depends only on the tags belonging to the same type of control (which in my case was fine because I was using Border for the error messages):

        public static IEnumerable<T> FindVisualChildren<T>(DependencyObject depObj) where T : DependencyObject
        {
            if (depObj == null) yield return (T)Enumerable.Empty<T>();
            for (int i = 0; i < VisualTreeHelper.GetChildrenCount(depObj); i++)
            {
                DependencyObject ithChild = VisualTreeHelper.GetChild(depObj, i);
                if (ithChild == null) continue;
                if (ithChild is T t) yield return t;
                foreach (T childOfChild in FindVisualChildren<T>(ithChild)) yield return childOfChild;
            }
        }
        private void HideErrors()
        {
            foreach (Border temp in FindVisualChildren<Border>(mainApp))
            {
                if (temp.Tag != null && temp.Tag.ToString() == "err_box")
                {
                    temp.Visibility = Visibility.Collapsed;
                }
            }
        }

Findvisualchildren is a function that I found in this question: Find all controls in WPF Window by type

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 not_a_generic_user